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
oransible.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.
-
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.
- Install
-
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.
- In your project, make a
-
Write the Module Boilerplate Code:
- Create a new Python file (e.g.,
my_module.py
) inside thelibrary/
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()
- Create a new Python file (e.g.,
-
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 ormodule.fail_json()
for errors.
-
Arguments: Defined as
-
Add Module Documentation (Recommended):
- At the top of your script, include YAML-formatted
DOCUMENTATION
,EXAMPLES
, andRETURN
docstrings for clarity andansible-doc
support.
- At the top of your script, include YAML-formatted
-
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
-
Create a minimal playbook to run your module. Example:
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.
-
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.
- Create a
-
Initialize the Module File:
- Create a new Python file—e.g.,
my_standalone_module.py
—within thelibrary/
directory. - This filename will be the module's name when called from your playbooks.
- Create a new Python file—e.g.,
-
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.
- Module arguments (
-
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.
-
Create a simple playbook to validate your new module. Example:
-
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.
-
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.
-
Facts Modules: Automatically gather data from managed hosts such as OS details, network interfaces, disk space, and hardware info. The built-in
-
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"
- By default, Ansible collects facts at the start of a playbook, which you can disable by setting
-
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 }}"
-
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 }}
.
-
You can define your own custom facts during playbook execution:
-
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.
-
Place static or dynamic files in
-
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
-
Info modules (for example,
-
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.
-
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
-
In your Ansible project or collection, create a directory called
-
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
- Create the file
-
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.
- In your custom module script (e.g.,
-
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()
-
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.
-
Call the custom module from a playbook like this:
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.
-
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.
-
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).
- Create a
-
Role-Specific:
- Create a
library/
subdirectory within the particular role’s folder.
- Create a
-
Custom Path:
- Add paths by setting the
ANSIBLE_LIBRARY
environment variable or thelibrary
option inansible.cfg
.
- Add paths by setting the
-
System-Wide:
-
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).
-
Copy or write your custom module Python file, for example
-
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
-
To use a non-default path, update your
-
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.
-
Run
-
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
-
Reference the module by filename (without
-
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.
-
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.
-
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.
-
Use descriptive and consistent option names (for example, use
-
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.
-
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.
-
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.
- Detect invalid conditions quickly—exit with helpful messages using
-
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.
-
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.
-
Document Thoroughly:
- Include
DOCUMENTATION
,EXAMPLES
, andRETURN
docstrings at the top of each module for clarity andansible-doc
support. - Document all options, required parameters, default values, and typical usage.
- Include
-
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.
-
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.
-
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—usingmodule_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. 🚀