Creating Custom Modules
Relevant source files
The following files were used as context for generating this wiki page:
Creating custom modules within the opsmill.infrahub collection allows developers to extend Ansible's capabilities to interact with the Infrahub infrastructure data platform. This process involves creating module stubs, implementing logic through specific plugin patterns, and maintaining strict adherence to Ansible collection standards and Infrahub SDK abstractions.
Developers must choose between two primary architectural patterns: the Module-utils pattern for stateful operations (like node management) or the Action plugin pattern for controller-side API interactions (like GraphQL queries). All modules must include standardized documentation, examples, and return value definitions within their Python source files to support the project's automated documentation pipeline.
Sources: dev/guides/creating-a-module.md:1-12, AGENTS.md:1-10, dev/constitution.md:21-36
Architectural Patterns
Custom modules must follow one of two established patterns. Hybrid or custom patterns are prohibited by the project constitution.
Module-utils Pattern
Use this pattern for modules requiring state management, idempotency, and --diff support. The module stub in plugins/modules/ delegates core logic to an InfrahubModule subclass located in plugins/module_utils/. This pattern is used for managing Infrahub objects like nodes and branches.
Action Plugin Pattern
Use this pattern for modules that perform API calls directly on the Ansible controller, such as read-only queries or non-idempotent operations. The module stub is minimal, primarily handling documentation and argument validation, while the ActionModule in plugins/action/ executes the task.
Sources: dev/constitution.md:28-36, dev/knowledge/plugin-patterns.md:118-124, dev/guides/creating-a-module.md:12-16
| Feature | Module-utils Pattern | Action Plugin Pattern |
|---|---|---|
| Primary Use Case | Stateful CRUD (Create, Update, Delete) | API queries, artifacts, generate |
| Location of Logic | plugins/module_utils/<name>.py | plugins/action/<name>.py |
| Idempotency | Supported via fetch-compare-act | Generally not applicable |
| Check Mode | Supported (supports_check_mode=True) | Usually disabled |
| Diff Support | Supported | Not supported |
Sources: dev/constitution.md:28-36, dev/knowledge/plugin-patterns.md:118-124
Module Structure and Boilerplate
Every Python file in the collection must comply with specific Ansible and project-defined standards to pass sanity tests and ensure compatibility.
Required Headers and Imports
All plugin files must include a specific copyright header and Python 2/3 compatibility boilerplate:
- Copyright Header:
# Copyright (c) <year> Opsmilland a GPLv3 reference. - Future Imports:
from __future__ import absolute_import, annotations, division, print_function. - Metaclass:
__metaclass__ = type.
Sources: dev/constitution.md:10-18, plugins/AGENTS.md:12-20
SDK Import Guard
Access to the Infrahub API must be guarded by a conditional import pattern. This allows ansible-doc to parse the module even if the infrahub-sdk is not installed on the system.
try:
from infrahub_sdk import InfrahubClientSync
HAS_INFRAHUBCLIENT = True
except ImportError:
HAS_INFRAHUBCLIENT = False
Sources: plugins/AGENTS.md:52-62, dev/constitution.md:46-52
Component Relationship
The following diagram illustrates the relationship between the module stub, the chosen execution pattern, and the Infrahub SDK abstraction layer.
The diagram shows how a task routes through a module stub to either a module utils class or an action plugin, both of which utilize the standard SDK wrapper to communicate with Infrahub.
Sources: dev/guides/creating-a-module.md:8-16, dev/constitution.md:42-45, dev/knowledge/plugin-patterns.md:118-140
Implementation Steps
1. Create the Module Stub
Create a file at plugins/modules/<name>.py. This file contains the DOCUMENTATION, EXAMPLES, and RETURN docstrings in YAML format. It also initializes the AnsibleModule with the required argument_spec.
Sources: dev/guides/creating-a-module.md:20-25, dev/knowledge/plugin-patterns.md:8-12
2. Standard Argument Specification
Use INFRAHUB_ARG_SPEC for standardized connection parameters. This includes api_endpoint, token, timeout, branch, and validate_certs.
from copy import deepcopy
from ansible_collections.opsmill.infrahub.plugins.module_utils.infrahub_utils import INFRAHUB_ARG_SPEC
argument_spec = deepcopy(INFRAHUB_ARG_SPEC)
argument_spec.update(
# Add module-specific arguments here
)
Sources: plugins/AGENTS.md:41-50, plugins/modules/node.py:126-135
3. Implement Execution Logic
- For Action Plugins: Inherit from
ActionBaseinplugins/action/<name>.py. You must callsuper().run()and manually extract arguments fromself._task.args. - For Module Utils: Inherit from
InfrahubModuleinplugins/module_utils/<name>.py. Implement therun()method and utilize helper methods like_ensure_object_exists()for idempotency.
Sources: dev/guides/creating-a-module.md:92-113, dev/knowledge/plugin-patterns.md:143-165
Documentation and Release
The project uses an automated pipeline to generate reference documentation.
Documentation Generation
After modifying DOCUMENTATION, EXAMPLES, or RETURN docstrings, you must regenerate the MDX reference files.
- Run
invoke generate-docto extract docstrings and render them via Jinja2 templates. - Run
invoke docusaurusto verify the static site build.
Warning: Never hand-edit files indocs/docs/references/plugins/; these are generated artifacts.
Sources: dev/guidelines/documentation.md:14-27, dev/constitution.md:65-68
Release Preparation
Every new module or behavioral change requires a changelog fragment.
- Use
uv run towncrier create -c "Description" <issue>.added.mdto create a fragment in thechangelog/directory. - Fragments are assembled into the main
CHANGELOG.mdat release time.
Sources: dev/guides/creating-a-module.md:195-201, AGENTS.md:47-55
Summary Checklist
| Task | Requirement |
|---|---|
| Boilerplate | __metaclass__ = type and __future__ imports present. |
| SDK Guard | HAS_INFRAHUBCLIENT check implemented. |
| Security | no_log=True applied to token parameters. |
| Pattern | Module follows either Action or Module-utils pattern strictly. |
| Docs | invoke generate-doc run after docstring updates. |
| Fragments | Towncrier news fragment added in changelog/. |
Sources: dev/guides/creating-a-module.md:204-216, dev/constitution.md:78-95