How to create your Ansible module in Python

2020-11-27 0 Di Denis Monari

Ansible is probably the best Configuration Management tool currently available. In its simplest form it is an agentless automation tool that help you install and configure software. This is really the tip of the iceberg, because you can define configurations (system, software, server, whatever) as code, you can configure devices as long as you can reach them from a delegated machine. How far you want to push your configuration is up to you. You can go fully automated if you are brave enough, and completely remove the need to login to your systems. Ansible is not meant to define your hardware infrastructure, though. For that Terraform is probably better, but it is for sure one of the main actors, and a good synergy between them is the best way for your hybrid cloud.

Ansible ship with a plethora of modules ready for you to use, ranging from package manager (yum, apt, etc) to archiver, download, text file editing, template, etc. If something is missing or you need something very specific you can even build your own module. Because Ansible is basically written in Python, the easiest way to create a module is using Python.

Here you can find the Ansible documentation on how to setup your own module, but let me show you how easy is to create one.

You have basically two options: write your modules directly within the Ansible role or playbook, or provision your libraries independently and make them available in the virtual environment where Ansible will run on.

#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule

from my_library.my_sublibrary import myfunction

def main():
    fields = {
        "needed_parameter": {"required": True, "type": "str"},
        "optional_parameter": {"required": False, "type": "str", "default": None}
    }

    module = AnsibleModule(argument_spec=fields)

    result = myfunction(first_parameter=module.params.get('needed_parameter'),
        second_parameter=module.params.get('optional_parameter')
    )

    output = {
        "returncode": result.my_function_RC,
        "stdout": result.my_function_STDOUT,
        "stderr": result.my_function_STDERR
    }

    if result.my_function_RC == "OK":    
        module.exit_json(changed=result.my_function_RC, meta=output)
    else:
        module.fail_json(msg=result.my_function_STDERR)


if __name__ == '__main__':
    main()

Let’s see the second option. The above code is probably the bare minimum. It is the full content of a .py file you should place under library folder within the Ansible role. The first import is required to load the Ansible module to interoperate, the second import load myfunction from my_library.my_sublibrary that is visibile in the virtual environment from where Ansible is executed. The output is a class with few attributes required to compose a dictionary needed by AnsibleModule to manage the outcome of the whole operation. As you can see, in the main definition you list the signature that will be available from the Ansible tasks; the two parameters have attributes like type, default and if they are required or not.

To use your newly created module, just reference it inside your tasks. Its name will be the filename. See this ex:

- name: running my new module
  become_user: ansible
  my_new_module:
      needed_parameter: "{{one_needed_parameter}}"
      optional_parameter: "{{one_optional_parameter | default(omit)}}"

This is barely a sort of wrapper around an external library, but the architecture is useful to increase the reusability of your code. If the same module is required by multiple roles and playbook, you won’t need to maintain multiple copies of the same code within different roles because the core algorithm is outside the Ansible role.

You can use pipenv to setup your Python virtual environment, including all your library dependencies. Just write your Pipfile (better if you use Python 3), and setup the virtual environment on your machine (you can use Ansible to do that too!). Once the virtual environment is ready, tells Ansible to use that when he needs to run your module. To do that, ensure to set this variable within your role:

ansible_python_interpreter: /full/path/to/my/virtualenv/.venv/bin/python3

That role will see all the libraries loaded in the virtual environment, along with the specific Python version, defined in the Pipefile. Bear in mind where you “delegate” your Ansible task: your library must reside in the delegated host, or they won’t be accessible by the module! Also remember that, in a playbook where you run multiple roles in sequence, you may get in trouble if different roles needs different virtual environments or different Python versions. I strongly suggest to standardize and/or test carefully before going blindly.

Conclusion

Ansible is so powerful and easy to learn and integrate, you will quickly discover you will not be able to live without it anymore. It is both precise and reliable, fast and easy to develop. The fact you don’t need any agent is a great plus. Roles and playbook will quickly become your systems definitions, helping you building a reliable software infrastructure. You will start versioning your software infrastructure and you will be able to re-create it exactly identical when a disaster occurs (well, at least it will be a faster recovery).

Think.. how far and deep can you go with cx_Oracle and Ansible with your Oracle Python modules? 😉