Ansible Pilot

Creating a Custom Ansible Lookup Plugin in Python for retrieving API token

Extending Ansible Functionalities with Custom Lookup Plugins for retrieving API tokens.

September 11, 2023
Access the Complete Video Course and Learn Quick Ansible by 200+ Practical Lessons

Introduction

Ansible, an open-source automation tool, offers a wide range of built-in modules and plugins to simplify infrastructure management. However, sometimes, you may need to extend its functionality by creating custom plugins tailored to your specific needs. In this article, we’ll explore the creation of a custom Ansible lookup plugin in Python.

What is a Lookup Plugin?

Ansible lookup plugins are used to retrieve data dynamically during playbook execution. They allow you to fetch information from various sources, such as databases, APIs, or external files, and use that data in your Ansible tasks.

The Best Resources For Ansible

Certifications

Video Course

Printed Book

eBooks

Step by Step

The following steps explain how to create a custom Ansible lookup plugin in Python. It begins by introducing lookup plugins in Ansible, which are used to fetch data dynamically during playbook execution. The provided Python script is an example of a plugin designed to retrieve an API token from a specific URL. The following steps break down the script into its components: Python headers, documentation, imports, initialization, and the custom lookup module with its ‘run’ method.

Setting the Stage

Let’s consider the example of retrieving a token via an API request with a POST specifying email and password. This is a common behavior. In the following example, we are using the following endpoint https://reqres.in/api/login with the credentials: “email”: “[email protected] and “password”: “cityslicka”. A successful connection to the API returns the token QpwL5tke4Pnpja7X4.

You can find the full code at the end of the article. Let’s take a closer look at the Python script provided at the beginning of this article. This script is an example of a custom Ansible token.py lookup plugin designed to fetch an API token from a specific URL. Here’s a breakdown of its components:

1. Python 3 Headers

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

These lines specify Python 3 headers required for compatibility when submitting this plugin to Ansible.

2. Documentation

DOCUMENTATION = r"""
  name: test
  author: Luca Berton <[email protected]>
  version_added: "0.1"  # same as collection version
  short_description: read API token
  description:
      - This lookup returns the token from the provided API.
"""

This block provides metadata about the plugin, including its name, author, version, and a short description. It’s essential for documentation and readability.

3. Imports

from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display
import requests

The script imports necessary modules and classes from Ansible and external libraries. These imports enable the plugin to handle errors, access Ansible utilities, and make HTTP requests using the requests library.

4. Initialization

display = Display()

The Display class is used for displaying messages and debugging information within Ansible. An instance of this class is created for logging purposes.

5. Custom Lookup Module

class LookupModule(LookupBase):

Here, we define our custom lookup module, named LookupModule, which inherits from LookupBase, a base class for creating lookup plugins in Ansible.

6. Custom Method (run)

def run(self, terms, variables=None, **kwargs):
    # Plugin logic goes here

The run method is the heart of the lookup plugin. It defines how the plugin operates when invoked in an Ansible playbook. In this example, it makes an HTTP POST request to a specified URL, extracts an API token from the response, and handles errors gracefully.

Please note that by design, Ansible Lookup plugins are historically usually used to pipe results into loops. Ansible expects a list as a return type. A common walkaround is to wrap our return value into a list, like:

return [ret]

The full code of the token.py Ansible lookup plugin looks like the:

# python 3 headers, required if submitting to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r"""
  name: test
  author: Luca Berton <[email protected]>
  version_added: "0.1"  # same as collection version
  short_description: read API token
  description:
      - This lookup returns the token from the provided API.
"""
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display
import requests

display = Display()

class LookupModule(LookupBase):

    _URL_ = "https://reqres.in/api/login"

    def run(self, terms, variables=None, **kwargs):
      payload = {
        "email": "[email protected]",
        "password": "cityslicka"
      }
      try:
        res = requests.post(self._URL_, data=payload)
        res.raise_for_status()
        ret = res.json()['token']

      except requests.exceptions.HTTPError as e:
        raise AnsibleError('There was an error getting a token. The lookup API returned %s', response.status_code)
      except Exception as e:
        raise AnsibleError('Unhandled exception is lookup plugin. Origin: %s', e)

      return [ret]

Enabling lookup plugins

Ansible automatically enables all available lookup plugins. To activate a custom lookup plugin, you can do so by placing it in one of the following locations:

  1. Adjacent to Your Playbook: You can include your custom lookup plugin in a directory called lookup_plugins located next to your playbook file.

  2. Inside a Collection: If you’ve installed an Ansible collection, you can place your custom lookup plugin in the plugins/lookup/ directory within that collection.

  3. Within a Standalone Role: Custom lookup plugins can also be included within a standalone Ansible role. Place the plugin in the appropriate lookup_plugins directory within the role.

  4. Configured Directory Sources: If you have configured custom directory sources in your ansible.cfg configuration file, you can place the lookup plugin in one of those directories. The setting key is lookup_plugins under the [defaults] section or ANSIBLE_LOOKUP_PLUGINS environmental variable

By following these methods, you can activate and use custom lookup plugins in Ansible to extend its functionality according to your specific automation requirements.

Using the Custom Lookup Plugin

Now that we’ve dissected the token.py script let’s discuss using this custom lookup plugin in an Ansible playbook. Here are the key steps:

Place the Plugin File: Save the Python script containing your custom lookup plugin in a directory recognized by Ansible (e.g., ./lookup_plugins/ within your Ansible project directory). Include the Lookup in a Task:

- name: Fetch API Token
  ansible.builtin.debug:
    msg: "{{ lookup('token') }}"

In your playbook, use the lookup function to invoke your custom plugin. In this example, we call the plugin named ’token’ and display the returned token using the Ansible debug module.

Ansible Playbook

The full Ansible Playbook code is the following:

---
- name: Exec lookup plugin
  hosts: all
  tasks:
    - name: Retrieve token from plugin
      ansible.builtin.debug:
        msg: "{{ lookup('token') }}"

Execution

ansible-playbook -i inventory exec.yml

PLAY [Exec lookup plugin] *******************************************************************************

TASK [Gathering Facts] **********************************************************************************
ok: [demo.example.com]

TASK [Retrieve token from plugin] ***********************************************************************
ok: [demo.example.com] => {
    "msg": "QpwL5tke4Pnpja7X4"
}

PLAY RECAP **********************************************************************************************
demo.example.com                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As expected, we obtained the QpwL5tke4Pnpja7X4 token on the screen due to the API request.

Conclusion

Creating custom lookup plugins in Ansible allows you to extend its capabilities and interact with various data sources dynamically. By understanding the structure of a lookup plugin and the provided Python script, you can craft your own custom plugins to streamline your automation workflows and meet your specific requirements. Whether it’s fetching API tokens or retrieving data from other sources, custom lookup plugins empower you to harness the full potential of Ansible for your infrastructure automation needs.

Subscribe to the YouTube channel, Medium, and Website, X (formerly Twitter) to not miss the next episode of the Ansible Pilot.

Academy

Learn the Ansible automation technology with some real-life examples in my

My book Ansible By Examples: 200+ Automation Examples For Linux and Windows System Administrator and DevOps

BUY the Complete PDF BOOK to easily Copy and Paste the 250+ Ansible code

Want to keep this project going? Please donate

Access the Complete Video Course and Learn Quick Ansible by 200+ Practical Lessons
Follow me

Subscribe not to miss any new releases