Mantra Networking Mantra Networking

Ansible: Modules

Ansible: Modules
Created By: Lauren R. Garcia

Table of Contents

  • Overview
  • What Is an Ansible Module?
  • Developing Custom Ansible Modules
  • Creating a Standalone Python Module
  • Info and Facts Modules
  • Using Module Utilities
  • Adding Local Modules
  • Key Module Development Best Practices
  • Conclusion

Ansible Modules: Overview

What Is an Ansible Module?

Ansible modules are the core building blocks behind every Ansible automation task. Think of them as self-contained scripts or tools that execute discrete jobs, such as configuring network devices, managing files or services, deploying applications, or collecting system information. Every action you automate with Ansible relies on one or more modules to carry out the heavy lifting on managed hosts.

Why You Need to Know About Ansible Modules

  • Essential for Automation: All playbooks and ad-hoc tasks in Ansible rely on modules—understanding modules is fundamental to using Ansible effectively.
  • Customization and Flexibility: While Ansible ships with hundreds of built-in modules, knowing how modules work lets you select the right ones or develop custom solutions tailored to your infrastructure.
  • Idempotence and Reliability: Modules are typically idempotent—meaning repeated runs won’t cause unwanted changes. This is crucial for reliable, predictable automation.
  • Efficiency: Well-written modules control how changes are made, report meaningful results, and provide options for dry runs (check mode), which helps prevent errors and streamline maintenance.
  • Platform Agnostic: Modules allow you to automate across a wide variety of environments—Linux, Windows, network devices, cloud APIs—without having to learn vendor-specific automation tools for each platform.

How Ansible Modules Work

  • Execution: When you run an Ansible playbook, each task invokes a specific module, passing it parameters defined in the playbook. Ansible then copies the module (if necessary) to the target host and executes it, often over SSH or APIs.
  • Input & Output: Modules accept arguments (parameters) that control task behavior. After execution, they return information—such as whether changes occurred, status messages, or collected data—back to Ansible in a structured format (JSON).
  • Language Flexibility: Most official modules are written in Python, but modules can be written in any language capable of standard input/output, making them highly flexible for custom development.
  • Modular Reuse: Since each module is a single-purpose tool, you can mix and match them to build complex workflows—everything from basic configuration changes to advanced, multi-step orchestration.
  • Extendability: If existing modules don't fit your needs, you can write your own modules, integrate shared utilities, and even distribute your creations within your organization or the wider community.

By mastering Ansible modules, you unlock the potential to automate any aspect of network, server, or cloud infrastructure in a way that is powerful, repeatable, and easily maintained.

What Is an Ansible Module?

An Ansible module is a standalone script or executable that performs a specific automation task within an Ansible playbook. Modules are the building blocks of Ansible's automation, responsible for everything from configuring network devices and updating packages to collecting system facts and managing users.

  • Lightweight Execution: Modules are designed to execute quickly and efficiently. Each module is invoked as needed by Ansible and then removed from memory, ensuring minimal impact on target systems.
  • Idempotence: Modules are typically idempotent, meaning they are designed to achieve the same result even if run multiple times. This guarantees that repeated automation does not cause unintended changes.
  • Language Flexibility: While most official modules are written in Python, modules can be developed in any language that can read input from standard input (stdin) and emit JSON output.
  • Arguments & Return Values: Each module accepts arguments (parameters) defined by the user and returns the outcome in a structured JSON format. This allows for clear communication between the playbook and the task being performed.
  • Usage in Playbooks: Modules are called within playbooks through tasks, such as ansible.builtin.copy or ansible.builtin.user, specifying arguments relevant to the automation job at hand.
  • Examples of Core Modules: Some popular core modules include:
    • copy: Uploads files to remote machines.
    • file: Manages file attributes or directories.
    • user: Manages user accounts and groups.
    • service: Controls system services.
    • yum/apt: Manages package installation for Linux distributions.

In summary, Ansible modules provide the essential task automation capabilities within Ansible, allowing IT engineers to automate operations reliably and at scale.

Developing Custom Ansible Modules

Creating your own Ansible module can extend automation capabilities and tailor tasks to unique network or infrastructure requirements. Follow this step-by-step approach to build a custom module using Python, Ansible’s most common language for module development.

  1. Set Up Your Development Environment:
    • Install ansible-core and Python (3.x recommended).
    • Use a virtual environment for module development: python -m venv venv and activate it.
  2. Create the Module Directory:
    • In your project, make a library/ directory. This is where your custom module scripts will live.
    • Place your test playbook in the same folder for easy testing.
  3. Write the Module Boilerplate Code:
    • Create a new Python file (e.g., my_module.py) inside the library/ directory.
    • Start with this template:
    #!/usr/bin/python
    
    from ansible.module_utils.basic import AnsibleModule
    
    def run_module():
        module_args = dict(
            name=dict(type='str', required=True),
            new=dict(type='bool', required=False, default=False)
        )
    
        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)
    
        result['original_message'] = module.params['name']
        result['message'] = 'Processed in module'
    
        if module.params['new']:
            result['changed'] = True
    
        module.exit_json(**result)
    
    def main():
        run_module()
    
    if __name__ == '__main__':
        main()
    
  4. Understand the Key Components:
    • Arguments: Defined as module_args. Each parameter your module will accept should be listed here.
    • AnsibleModule: This helper class handles argument parsing, check mode support, and result formatting.
    • Result Dictionary: Populate this with the data you want to return (e.g., changed, message).
    • Exit Methods: Use module.exit_json() on success or module.fail_json() for errors.
  5. Add Module Documentation (Recommended):
    • At the top of your script, include YAML-formatted DOCUMENTATION, EXAMPLES, and RETURN docstrings for clarity and ansible-doc support.
  6. Test Your Custom Module:
    • Create a minimal playbook to run your module. Example:
      ---
      - hosts: localhost
        tasks:
          - name: Test my custom module
            my_module:
              name: HelloWorld
              new: true
            register: module_result
          - debug: var=module_result
      
    • Run the playbook to see your module in action: ansible-playbook test_playbook.yml

By following these steps, you can create, document, and test your own Ansible modules to automate network, server, or cloud tasks unique to your environment.

Creating a Standalone Python Module

Building a standalone Python module for Ansible enables you to create custom automation tasks tailored to your environment. This step-by-step guide walks you through creating your own module from scratch.

  1. Organize Your Workspace:
    • Create a library/ directory in your project folder. All custom modules will reside here.
    • Place your test playbook in the same directory for easy access.
  2. Initialize the Module File:
    • Create a new Python file—e.g., my_standalone_module.py—within the library/ directory.
    • This filename will be the module's name when called from your playbooks.
  3. Write the Module Code:
    • Begin your script with the Ansible Python module boilerplate. Here’s an example template:
    #!/usr/bin/python
    
    from ansible.module_utils.basic import AnsibleModule
    
    def run_module():
        module_args = dict(
            param1=dict(type='str', required=True),
            param2=dict(type='int', required=False, default=0)
        )
    
        result = dict(
            changed=False,
            msg='',
            param1='',
            param2=0
        )
    
        module = AnsibleModule(
            argument_spec=module_args,
            supports_check_mode=True
        )
    
        if module.check_mode:
            module.exit_json(**result)
    
        result['param1'] = module.params['param1']
        result['param2'] = module.params['param2']
        result['msg'] = 'Module ran successfully'
    
        # Example: set changed if param2 > 0
        if module.params['param2'] > 0:
            result['changed'] = True
    
        module.exit_json(**result)
    
    def main():
        run_module()
    
    if __name__ == '__main__':
        main()
    
    • This template defines:
      • Module arguments (module_args), including types and required status.
      • A result dictionary for output data and status.
      • Check mode support for safe dry-runs.
      • Logic for successful completion or marking a change.
  4. Test the Module:
    • Create a simple playbook to validate your new module. Example:
      ---
      - hosts: localhost
        tasks:
          - name: Run standalone Python module
            my_standalone_module:
              param1: "Hello Module"
              param2: 1
            register: custom_result
      
          - debug: var=custom_result
      
    • Run the playbook in your environment and ensure your module performs as expected.
  5. Iterate and Extend:
    • Add more logic, parameters, or error handling as your infrastructure demands evolve.
    • Document module arguments and return values for clarity and maintainability.

With these steps, you can create effective, reusable custom Python modules for Ansible, enhancing your automation toolkit with powerful, purpose-built tasks.

Info and Facts Modules

Ansible’s “info” and “facts” modules enable you to dynamically collect system information, making playbooks more flexible and context-aware. Here’s a step-by-step walkthrough on understanding and using these modules.

  1. Understanding Info and Facts Modules:
    • Facts Modules: Automatically gather data from managed hosts such as OS details, network interfaces, disk space, and hardware info. The built-in setup module is the most common facts module.
    • Info Modules: Provide details about external environments or resources (like cloud, network, or device info). Their names often end with _info or _facts but do not alter system state.
  2. Gathering Built-in Facts:
    • By default, Ansible collects facts at the start of a playbook, which you can disable by setting gather_facts: no.
    • To manually gather and inspect facts:
      ansible all -m setup
    • Use fact filters to narrow down output for readability:
      ansible all -m setup -a "filter=ansible_hostname"
  3. Accessing Facts in Playbooks:
    • Facts are available as variables. Example:
    - name: Show the host’s OS
      debug:
        msg: "This host runs {{ ansible_distribution }} {{ ansible_distribution_version }}"
    
  4. Custom Facts with set_fact:
    • You can define your own custom facts during playbook execution:
      - name: Set a custom fact
        set_fact:
          custom_role: "database"
      
    • Reference them later in your playbook using {{ custom_role }}.
  5. Host-based Custom Facts:
    • Place static or dynamic files in /etc/ansible/facts.d/ on target hosts using JSON or INI format for persistent custom facts.
    • Re-run the setup module to collect these custom facts.
  6. Info Modules in Action:
    • Info modules (for example, aws_ec2_info, service_facts) gather resource data and make it accessible as variables.
    • Example:
      - name: Gather service info
        service_facts:
        - name: Print all running services
          debug:
            var: ansible_facts.services
      
  7. Best Practices:
    • Use facts to write conditional and dynamic tasks.
    • Limit gathered facts to only what’s needed for efficiency.
    • Document custom facts clearly for maintainability.

By leveraging info and facts modules, your playbooks become more intelligent and adaptive, ensuring accurate automation based on real-time system data.

Using Module Utilities

Module utilities in Ansible are reusable Python code components that extend the functionality of your custom modules and help you avoid code duplication. They’re particularly useful for managing shared logic across multiple modules. Follow this step-by-step guide to integrate and use module utilities effectively.

  1. Create a module_utils/ Directory:
    • In your Ansible project or collection, create a directory called module_utils. This is where helper functions, common classes, and shared logic will be stored.
    • Example structure:
      my_project/
      ├── library/
      │   └── my_module.py
      ├── module_utils/
      │   └── helper.py
      
  2. Write a Python Utility Module:
    • Create the file module_utils/helper.py and add your reusable functions.
    # helper.py
    
    def format_hostname(name):
        return name.lower().replace(" ", "_")
    
    def is_valid_ip(address):
        import ipaddress
        try:
            ipaddress.ip_address(address)
            return True
        except ValueError:
            return False
    
  3. Import the Utility in Your Custom Module:
    • In your custom module script (e.g., library/my_module.py), import the utility:
    from ansible.module_utils.helper import format_hostname, is_valid_ip
    
    • Use the imported functions in your logic as needed.
  4. Example Usage in Custom Module:
    #!/usr/bin/python
    
    from ansible.module_utils.basic import AnsibleModule
    from ansible.module_utils.helper import format_hostname, is_valid_ip
    
    def run_module():
        module_args = dict(
            hostname=dict(type='str', required=True),
            ip_address=dict(type='str', required=True)
        )
    
        module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
        hostname = module.params['hostname']
        ip = module.params['ip_address']
    
        if not is_valid_ip(ip):
            module.fail_json(msg="Invalid IP address format")
    
        transformed_name = format_hostname(hostname)
    
        result = dict(
            changed=False,
            original=hostname,
            transformed=transformed_name,
            ip=ip
        )
    
        module.exit_json(**result)
    
    def main():
        run_module()
    
    if __name__ == '__main__':
        main()
    
  5. Run and Test:
    • Call the custom module from a playbook like this:
      ---
      - hosts: localhost
        tasks:
          - name: Test module utility usage
            my_module:
              hostname: "My New Host"
              ip_address: "192.168.1.1"
            register: result
      
          - debug: var=result
      
    • Ensure output reflects the use of utility functions properly.

By using module utilities, you promote clean, modular code and make it easier to maintain and scale your custom Ansible modules across large environments.

Adding Local Modules

Expanding Ansible with local modules lets you incorporate custom automation quickly for your team or project. Here’s a step-by-step guide for adding local modules to your Ansible workflow.

  1. Decide the Scope of Availability:
    • For all playbooks and roles: Make the module available system-wide or user-wide.
    • For selected playbooks: Limit the module's use within a particular directory or repository.
    • For a single role: Embed the module only inside a specific role's directory.
  2. Choose the Appropriate Directory:
    • System-Wide:
      • /usr/share/ansible/plugins/modules/ (requires admin privileges)
    • User-Wide:
      • ~/.ansible/plugins/modules/
    • Project-Specific:
      • Create a library/ folder in your project directory (next to your playbooks).
    • Role-Specific:
      • Create a library/ subdirectory within the particular role’s folder.
    • Custom Path:
      • Add paths by setting the ANSIBLE_LIBRARY environment variable or the library option in ansible.cfg.
  3. Add Your Module File:
    • Copy or write your custom module Python file, for example my_custom_module.py, into your chosen directory.
    • The module name in tasks/playbooks matches the Python filename (without the .py extension).
  4. Update Configuration (If Needed):
    • To use a non-default path, update your ansible.cfg:
      [defaults]
      library = ./library
      
    • Or set the environment variable:
      export ANSIBLE_LIBRARY=./library
      
  5. Verify Module Availability:
    • Run ansible-doc -t module my_custom_module to confirm Ansible recognizes your module and shows its documentation.
    • Alternatively, test with ansible localhost -m my_custom_module to execute the module.
  6. Use Your Module in Playbooks and Roles:
    • Reference the module by filename (without .py), just like built-in modules:
      ---
      - hosts: localhost
        tasks:
          - name: Custom task with local module
            my_custom_module:
              option: value
      
  7. Best Practices:
    • Document each custom module clearly for future users.
    • Organize modules by keeping them in dedicated library/ directories.
    • Share frequently used modules via version control for consistency across teams.

By following these steps, you can efficiently integrate and manage local modules within any Ansible project, ensuring modular, reusable, and easily testable automation workflows.

Key Module Development Best Practices

Following best practices when developing Ansible modules ensures reliability, maintainability, and seamless integration with other playbooks. Use this step-by-step approach as a checklist for developing robust custom modules.

  1. Define Clear Functionality:
    • Follow the “do one thing well” philosophy by keeping your module’s scope focused and concise.
    • Avoid feature creep—don’t add unrelated tasks or logic into a single module.
  2. Design Predictable Interfaces:
    • Use descriptive and consistent option names (for example, use name for addressing objects).
    • Offer aliases for parameters if an external API uses different names, but maintain internal clarity.
    • Support common Ansible option types, such as state for present/absent/started/stopped logic.
  3. Support Idempotency and Check Mode:
    • Write your module to be idempotent—running it multiple times should not cause changes unless required.
    • Implement check_mode so users can perform dry runs without making changes.
  4. Validate and Sanitize Input:
    • Use strict argument definitions to catch bad input early with clear error messages.
    • Convert and validate booleans, paths, or data structures robustly for predictable results.
  5. Fail Fast and Clearly:
    • Detect invalid conditions quickly—exit with helpful messages using module.fail_json().
    • Return errors as soon as an issue is found to prevent unintended system changes.
  6. Keep Dependencies Minimal:
    • Limit your reliance on external libraries. If dependencies are required, document them clearly at the top of your module.
    • Gracefully handle missing dependencies and provide actionable installation guidance when possible.
  7. Provide Structured, Relevant Output:
    • Return only the information users need. Avoid dumping verbose logs—use structured JSON responses for easy consumption.
    • Accurately indicate whether the module made changes with the changed flag.
    • For info/facts modules, return values inside ansible_facts for consistency.
  8. Document Thoroughly:
    • Include DOCUMENTATION, EXAMPLES, and RETURN docstrings at the top of each module for clarity and ansible-doc support.
    • Document all options, required parameters, default values, and typical usage.
  9. Test Rigorously:
    • Test all logic paths, including error handling, check mode, and complex scenarios.
    • Leverage ansible-test or custom playbooks to validate module behavior before release.
  10. Follow Naming Conventions:
    • Use underscores in module filenames and avoid spaces or hyphens to ensure compatibility and auto-discovery.
    • Maintain consistent naming for parameters and return values across modules.
  11. Minimize Side Effects:
    • Avoid performing actions outside the module’s explicit purpose.
    • Use temporary files and atomic operations for any file manipulations, preventing corruption or partial changes.

Applying these best practices will make your custom Ansible modules reliable, easy to use, and maintainable in complex automation environments.

Conclusion

Throughout this blog post, we’ve taken a deep dive into the powerful world of Ansible Modules, uncovering how they serve as the functional core of any playbook or automation strategy. Let’s quickly recap what we’ve learned:

  • What Is an Ansible Module?
    We explored how modules are standalone units that perform specific tasks and how Ansible leverages them to maintain consistency, idempotence, and ease of use across automation projects.
  • Developing Custom Modules:
    We went step-by-step into creating your own custom module using Python—perfect for solving environment-specific problems.
  • Creating Standalone Python Modules:
    You learned how to build self-contained, reusable modules and integrate them with Ansible seamlessly.
  • Info and Facts Modules:
    We covered how to gather dynamic environment data using built-in or custom facts and info modules, making your automation context-aware and intelligent.
  • Using Module Utilities:
    Writing clean modules means modularizing your logic—using module_utils to share code and simplify maintenance across custom modules.
  • Adding Local Modules:
    Whether you're working in a single playbook or a full collection, we shared how to structure, load, and validate local custom modules.
  • Key Module Development Best Practices:
    We wrapped up module development with tips to ensure reliability, security, performance, and code hygiene through consistent naming, validation, check mode, and error handling.

Ansible modules are more than just tasks—you’re building reusable infrastructure logic. With your own modules and utilities in hand, you can tailor automation to your organization's exact needs without relying solely on built-in functionality.

We hope this guide empowers you to not only use Ansible more effectively but also contribute your own custom modules across your team or the broader community.

Thanks for reading—and happy automating! If you found this helpful, consider sharing it or bookmarking for future reference. Feel free to drop a comment or revisit if you’re refining your own modular Ansible toolkit. 🚀