Nebulaworks Insight Content Card Background - Pontus wellgraf tile metal

Nebulaworks Insight Content Card Background - Pontus wellgraf tile metal

Creating Ansible Modules - A Reusable Framework

July 30, 2019 Drew Mullen

Learn how to leverage Python to create custom Ansible modules with proper idempotence, support for check mode, and "state" management.

Recent Updates

I love Ansible. It has so many features that make my work easier, automated, and highly readable as a Cloud Engineer at Nebulaworks. But what about when the OOTB modules are not cutting it? What about when I find my playbook with 10+ uri module calls and each play is 12 lines of code; idempotence, readability have flown out the window! No more, I say! In this post, I’m going to provide you an easily repeatable framework for creating custom Ansible modules. I think you will find it simple to do, fun, and can improve playbook quality significantly. I take into account the major benefits of Ansible and we’ll ensure these benefits:

Disclaimer: consider whether or not you should make a module before doing so

Supporting check mode

First a quick explanation on why you should support check mode and how it dictates the design of this framework.

One of the major arguments for even using a tool like Ansible is the ability to declare state but not necessarily take action. This is the basic idea of idempotence. Check mode takes this idea up one level and allows you to simply test whether or not any changes WOULD be made. This can be useful in many ways, for example, have your playbooks run continuously in check mode against prod infrastructure and alert if a change WOULD take place.

In order to allow for check mode, all we need to do is separate making changes from checking if changes are required. I typically do this by implementing a few common variables:

Code

For example code, you can reference Ansible Module 101

Anatomy of a module

  1. AnsibleModule class instantiation
  2. Comparison of the current and desired state
  3. Taking Action
  4. Report

1. AnsibleModule class instantiation

Ansible provides the AnsibleModule class for easy extension of the tool. We start off any module by instantiating this class and passing the modules variables from our play.

Simple Example:

def main():
    module = AnsibleModule(
        argument_spec=dict(
            message=dict(type='str'),
            state=dict(type='str', choices=['present','absent'], default='present')
        ),
        required_if=([('state', 'present', ['message'])]),
        supports_check_mode=True
    )

For a list of the accepted parameters to AnsibleModule, check the source here

When the above python file is called by an Ansible play, it will start by creating a module instance. With the returned object module we can now perform the rest of our logic and end by returning changed: True / False. We will actually use a variable to track so changed: changed.

2. Exploration of current state

In order to be idempotent, we must next examine the current state of the object we’re attempting to manage. I also take this time to make checks on the environment to ensure and requisites are already present. Whatever object is attempting to be managed, perform checks:

Does it exist?

exists = os.path.exists(motd_file)

If it exists and is desired state is ‘present’ check current content vs desired

if exists and module.params['state'] == 'present':
    # get current message from file
    with open(motd_file, 'r') as fd:
        current_message = fd.read().strip()
    # check current content vs desired
    if current_message != message:
        changed = True

# file exists but we don't want it to
elif exists and module.params['state'] == 'absent':
    changed = True

# file doesn't exist but we want it to
else:
    if module.params['state'] == 'present':
        changed = True

3. Taking Action

Now we have all the information we need to make idempotent respectful changes. Last we’ll account for check mode and skip over taking action if directed.

# only perform changes when a required change has been detected and if check mode is off
if changed and not module.check_mode:
    if module.params['state'] == 'present':
        with open(motd_file, 'w') as fd:
            fd.write(module.params['message'])
    else:
        os.remove(motd_file)

4. Report

Finally, we can report back to Ansible on what we’ve done. The goal of check mode is to state changed = True if we WOULD have completed a change but we do not complete it. With the code design in our [taking action](### Taking Action) section, we have accounted for that requirement.

module.exit_json(changed=changed)

Final Thoughts

Now you know how to create a repeatable framework for Ansible modules! For all things automation and Devops, Nebulaworks provides expert guidance, training, and services. Hit us up if you’re looking for help!

Insight Authors

Drew Mullen, Sr. Cloud Engineer Drew Mullen Sr. Cloud Engineer
Nebulaworks - Wide/concrete light half gray

Looking for a partner with engineering prowess? We got you.

Learn how we've helped companies like yours.