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