Unlock Unlimited Possibilities: Build Your Own Ansible Modules Today

Unlock Unlimited Possibilities: Build Your Own Ansible Modules Today

Creating your own custom Ansible module allows you to extend Ansible’s functionality to meet specific automation needs that aren’t covered by the existing modules. Below is a comprehensive guide to creating your own Ansible module, including an example to help you get started.

1. Understanding Ansible Modules

Ansible modules are reusable, standalone scripts that Ansible executes on remote nodes to perform specific tasks. While Ansible provides numerous built-in modules, custom modules are essential when you need to perform unique tasks tailored to your environment.

2. Prerequisites

Before creating a custom Ansible module, ensure you have the following:

  • Basic Knowledge of Ansible: Understanding of how playbooks, tasks, and modules work.
  • Programming Skills: Proficiency in Python (the most common language for Ansible modules) is highly recommended.
  • Ansible Development Environment: Ansible installed on your machine, along with a suitable text editor or IDE.

3. Steps to Create a Custom Ansible Module

Step 1: Define the Module’s Purpose

Before writing any code, clearly outline what your module will do. For this example, let’s create a simple module named greet that prints a greeting message.

Step 2: Set Up the Directory Structure

Organize your custom module within your Ansible project. It’s common to place custom modules in a library directory within your project.

your-ansible-project/
├── library/
│   └── greet.py
├── playbook.yml
└── inventory

Step 3: Write the Module Code

Here’s a step-by-step guide to writing the greet module in Python.

a. Basic Module Structure

Create a file named greet.py inside the library directory and add the following boilerplate code:

#!/usr/bin/python

from ansible.module_utils.basic import AnsibleModule

def run_module():
    # Define available arguments/parameters that a user can pass to the module
    module_args = dict(
        name=dict(type='str', required=True),
        greeting=dict(type='str', required=False, default='Hello')
    )

    # Seed the result dictionary
    result = dict(
        changed=False,
        original_message='',
        message=''
    )

    # Create the AnsibleModule object
    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    # If in check mode, exit without making changes
    if module.check_mode:
        module.exit_json(**result)

    # Retrieve parameters
    name = module.params['name']
    greeting = module.params['greeting']

    # Generate the greeting message
    message = f"{greeting}, {name}!"

    # Populate the result
    result['original_message'] = f"{greeting}, {name}!"
    result['message'] = message

    # Since this module does not change anything, 'changed' remains False
    module.exit_json(**result)

def main():
    run_module()

if __name__ == '__main__':
    main()

b. Explanation of the Code

  • Shebang Line: Specifies the interpreter to execute the script.
  • Imports: Imports the AnsibleModule class from Ansible’s module utilities.
  • run_module Function: Encapsulates the module logic.
    • module_args: Defines the arguments the module accepts (name and greeting).
    • result: A dictionary to store the output and status.
    • AnsibleModule: Initializes the module with the defined arguments and supports check mode.
    • Check Mode Handling: If Ansible runs in check mode, the module exits without making changes.
    • Parameter Retrieval: Gets the values of name and greeting from the user.
    • Message Generation: Creates the greeting message.
    • Result Population: Sets the original_message and message in the result.
    • Exit: Returns the result to Ansible.
  • main Function: Entry point of the module.

Step 4: Make the Module Executable

Ensure your module has executable permissions:

chmod +x library/greet.py

Step 5: Create a Playbook to Use the Custom Module

Create a playbook named playbook.yml to utilize the greet module.

---
- name: Test Custom Greet Module
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Send a greeting
      greet:
        name: "Alice"
        greeting: "Hi"
      register: greet_result

    - name: Display the greeting message
      debug:
        msg: "{{ greet_result.message }}"

Step 6: Run the Playbook

Execute the playbook using the ansible-playbook command.

ansible-playbook -i inventory playbook.yml

Expected Output:

PLAY [Test Custom Greet Module] ************************************************

TASK [Send a greeting] ********************************************************
ok: [localhost]

TASK [Display the greeting message] *******************************************
ok: [localhost] => {
    "msg": "Hi, Alice!"
}

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0   

Step 7: Testing the Module

Testing ensures your module works as expected. You can run the playbook multiple times with different parameters or use Ansible’s --check mode to verify behavior without applying changes.

ansible-playbook -i inventory playbook.yml --check

4. Enhancing Your Custom Module

Once you’re comfortable with creating basic modules, you can enhance them by:

  • Adding More Parameters: Include additional arguments to handle more complex scenarios.
  • Implementing State Management: Allow your module to handle different states (e.g., present/absent).
  • Error Handling: Use module.fail_json() to handle and report errors gracefully.
  • Idempotency: Ensure running the module multiple times doesn’t produce unintended side effects.

Example: Enhanced Greet Module with State

Let’s modify the greet module to handle different states, such as creating or deleting a greeting.

#!/usr/bin/python

from ansible.module_utils.basic import AnsibleModule

def run_module():
    module_args = dict(
        name=dict(type='str', required=True),
        greeting=dict(type='str', required=False, default='Hello'),
        state=dict(type='str', required=False, choices=['present', 'absent'], default='present')
    )

    result = dict(
        changed=False,
        original_message='',
        message=''
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    if module.check_mode:
        module.exit_json(**result)

    name = module.params['name']
    greeting = module.params['greeting']
    state = module.params['state']

    if state == 'present':
        message = f"{greeting}, {name}!"
        result['original_message'] = message
        result['message'] = message
        result['changed'] = True  # Assuming this action represents a change
    elif state == 'absent':
        result['message'] = f"Goodbye, {name}!"
        result['changed'] = True  # Assuming this action represents a change

    module.exit_json(**result)

def main():
    run_module()

if __name__ == '__main__':
    main()

Updated Playbook (playbook.yml):

---
- name: Test Enhanced Greet Module
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Create a greeting
      greet:
        name: "Bob"
        greeting: "Hello"
        state: "present"
      register: greet_result_present

    - name: Display the greeting message
      debug:
        msg: "{{ greet_result_present.message }}"

    - name: Remove the greeting
      greet:
        name: "Bob"
        state: "absent"
      register: greet_result_absent

    - name: Display the farewell message
      debug:
        msg: "{{ greet_result_absent.message }}"

Running the Updated Playbook:

ansible-playbook -i inventory playbook.yml

Expected Output:

PLAY [Test Enhanced Greet Module] ***********************************************

TASK [Create a greeting] *******************************************************
changed: [localhost]

TASK [Display the greeting message] ********************************************
ok: [localhost] => {
    "msg": "Hello, Bob!"
}

TASK [Remove the greeting] *******************************************************
changed: [localhost]

TASK [Display the farewell message] ********************************************
ok: [localhost] => {
    "msg": "Goodbye, Bob!"
}

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0   

5. Best Practices for Creating Custom Ansible Modules

  • Use Python Best Practices:
    • Follow PEP 8 style guidelines.
    • Write clear, maintainable, and well-documented code.
  • Provide Comprehensive Documentation:
    • Include a DOCUMENTATION string at the top of your module with details about parameters, usage, and examples.
    • Provide EXAMPLES and RETURN documentation sections.

Example: Adding Documentation to greet.py

#!/usr/bin/python

from ansible.module_utils.basic import AnsibleModule

DOCUMENTATION = '''
---
module: greet
short_description: Generates a greeting message.
description:
  - This module creates a greeting message based on the provided name and greeting.
options:
  name:
    description:
      - The name of the person to greet.
    required: true
    type: str
  greeting:
    description:
      - The greeting phrase.
    required: false
    default: "Hello"
    type: str
  state:
    description:
      - The state of the greeting.
    required: false
    choices: ['present', 'absent']
    default: 'present'
    type: str
author:
  - Your Name (@yourhandle)
'''

EXAMPLES = '''
- name: Send a greeting
  greet:
    name: "Alice"
    greeting: "Hi"

- name: Remove the greeting
  greet:
    name: "Alice"
    state: "absent"
'''

RETURN = '''
original_message:
  description: The original greeting message.
  type: str
  returned: success
message:
  description: The processed greeting message.
  type: str
  returned: success
'''

def run_module():
    # Module implementation remains the same
    ...

if __name__ == '__main__':
    main()
  • Ensure Idempotency:
    • Design modules so that running them multiple times produces the same result without unintended side effects.
  • Handle Errors Gracefully:
    • Use module.fail_json() to return meaningful error messages when something goes wrong.
  • Include Unit Tests:
    • Implement tests to verify your module’s functionality and handle edge cases.
  • Leverage Ansible’s Module Utilities:
    • Utilize Ansible’s helper functions and utilities for common tasks, such as argument validation.
  • Follow Naming Conventions:
    • Use clear and descriptive names for your modules to indicate their functionality.

6. Publishing Your Custom Module

If you believe your module could benefit others, consider publishing it:

  • Ansible Galaxy: Share your module on Ansible Galaxy to make it accessible to the community.
  • Version Control: Host your module on platforms like GitHub for version control and collaboration.

7. Additional Resources

By following this guide, you can create robust and reusable custom Ansible modules tailored to your specific automation needs. Happy automating!

Empower Your Automation Journey: Build Your Own Ansible Modules Today

Julio Dutra Avatar

Leave a Reply

Your email address will not be published. Required fields are marked *