Implementation of the Luhn Algorithm in Ansible

The Luhn or mod 10 algorithm is a simple check digit formula used to validate a variety of identification numbers. It is widely used for the validation of debit and credit card numbers. Its implementation in Ansible is fairly simple using plugins, which is an Ansible feature meant to “augment Ansible’s core functionality”.

Code

git clone https://github.com/andreypicado506/mod10_algorithm_ansible.git

Working with plugins in Ansible

According to the documentation, Ansible plugins must follow these rules:

  • be written in Python
  • raise errors
  • return strings in Unicode
  • conform to Ansible’s standards

These first three criteria are directly addressed in the Python code for our algorithm implementation. Additionally, we will structure the folder and invocation according to Ansible’s plugin standards. Given that our objective is to manipulate data, our implementation will take the form of a filter plugin. This approach ensures that our implementation is both consistent with Ansible’s guidelines and effectively serves its purpose.

Implementing Luhn algorithm on Python

To implement the Luhn algorithm in Python, we must create a function capable of running the algorithm on a given number. Since our implementation will adhere to the rules for Ansible plugins, it must seamlessly work with the PluginLoader. For practical insights into how to achieve this, Ansible’s source code offers valuable examples.

With these considerations in mind, here’s the code I’ve developed:

#!/usr/bin/python3

from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleUndefinedVariable, AnsibleFilterError
from ansible.module_utils.common.text.converters import to_native

def luhn_check_digit(number):
    try:
        reversed_number = str(number)[::-1]
        total = 0
        for index, digit in enumerate(reversed_number):
            digit = int(digit)
            if index % 2 == 1:
                digit *= 2
                if digit > 9:
                    digit -= 9
            total += digit
        check_digit = (10 - (total % 10)) % 10
        return str(check_digit)
    except UndefinedError as e:
        raise AnsibleUndefinedVariable(to_native(e))
    except Exception as e:
        raise AnsibleFilterError(to_native(e))

class FilterModule(object):
    def filters(self):
        return {'luhn_check_digit': luhn_check_digit}

Some notes about the code:

  • This plugin will be compatible with python3.
  • The imported libraries are required to handle errors according to the rules for Ansible plugins.
  • The Luhn algorithm just follows the pseudo-code that is widely available on the internet. You can find examples of it here or here. The function luhn_check_digit takes a number and returns 0 if it is valid. By default, the str type in python3 is Unicode.
  • The class FilterModule simply calls luhn_check_digit and is structured in a way that the Ansible PluginLoader can understand. From an Ansible playbook, the plugin can be called by referencing the name of the function.

Enabling the plugin

According to the Ansible documentation, “you can add a custom filter plugin by dropping it into a filter_plugins directory adjacent to your play, inside a role, or by putting it in one of the filter plugin directory sources configured in ansible.cfg.” In our case, we created a folder called filter_plugins in the same directory where our playbook is located:

├── filter_plugins
│   ├── luhn_filter.py
└── mod10_playbook.yaml

Calling the plugin from a playbook

Here’s a straightforward playbook that demonstrates how to use the plugin. In this example, we sanitize the user’s input by removing any non-numeric characters. However, it’s worth noting that our Python code also includes error handling to manage such scenarios:

---
- name: Calculate Luhn Check Digit
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Input number
      ansible.builtin.pause:
        prompt: "Enter the number to calculate the Luhn check digit for: "
      register: user_input

    - name: Remove non-numeric characters
      ansible.builtin.set_fact:
        cleaned_input: "{{ user_input.user_input }}"

    - name: Calculate Luhn check digit
      ansible.builtin.set_fact:
        luhn_digit: "{{ cleaned_input | luhn_check_digit }}"

    - name: Display Luhn check digit
      ansible.builtin.debug:
        var: luhn_digit
| Theme: UPortfolio