Ansible Custom Modules Development Prompt
Write custom Ansible modules in Python — module contract, AnsibleModule class, argument spec, idempotency, error handling.
- Target user
- Engineers extending Ansible with custom modules
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior Ansible engineer who has written custom modules for internal APIs and specialized workflows. I will provide: - The custom module purpose - Current implementation - Goal (write / improve / test) Your job: 1. **Module basics**: - Python script - Returns JSON - In `library/` (playbook-local) or `plugins/modules/` (collection) 2. **For module contract**: - Accept args - Use AnsibleModule class - Return result dict with `changed`, `failed` 3. **For argument spec**: - Define args (type, required, default) - Validation - `mutually_exclusive`, `required_together` 4. **For idempotency**: - Check current state - Only modify if different - Set `changed: true/false` 5. **For check mode**: - Module declares supports_check_mode - Compute changed without modifying 6. **For error handling**: - `module.fail_json(msg=...)` on error - Always include context 7. **For documentation**: - DOCUMENTATION (YAML) string - EXAMPLES - RETURN values - Used by ansible-doc 8. **For testing**: - Unit tests (pytest) - Integration tests (Molecule) - Sanity tests Mark DESTRUCTIVE: module without check mode support (no dry-run), error path leaving state changed, modules with side effects in import. --- Module purpose: [DESCRIBE] Current implementation: [DESCRIBE]
Why this prompt works
Custom modules extend Ansible. This prompt walks design.
How to use it
- Follow module contract.
- Support check mode.
- Document thoroughly.
- Test unit + integration.
Useful commands
# Test module locally
ansible localhost -m my_module -a "name=test state=present"
# Verbose
ansible localhost -m my_module -a "name=test" -vvvv
# Check ansible-doc
ansible-doc my_module
Module template
#!/usr/bin/python
# Copyright: (c) 2026, Your Name
# License: Apache 2.0
DOCUMENTATION = r'''
---
module: my_api_resource
short_description: Manage resources via Custom API
version_added: "1.0.0"
description:
- This module manages resources on Custom API.
options:
name:
description: Resource name
required: true
type: str
state:
description: Desired state
choices: [present, absent]
default: present
type: str
api_url:
description: API endpoint
required: true
type: str
api_token:
description: API token
required: true
type: str
no_log: true
author:
- Your Name (@yourgithub)
'''
EXAMPLES = r'''
- name: Create resource
my_org.my_collection.my_api_resource:
name: my-resource
state: present
api_url: https://api.example.com
api_token: "{{ vault_api_token }}"
- name: Delete resource
my_org.my_collection.my_api_resource:
name: my-resource
state: absent
api_url: https://api.example.com
api_token: "{{ vault_api_token }}"
'''
RETURN = r'''
resource:
description: Resource details
returned: when state=present
type: dict
sample:
id: "12345"
name: "my-resource"
status: "active"
changed:
description: Whether change occurred
returned: always
type: bool
'''
import requests
from ansible.module_utils.basic import AnsibleModule
def get_resource(module, name, url, token):
"""Check if resource exists."""
try:
r = requests.get(
f"{url}/resources/{name}",
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
if r.status_code == 200:
return r.json()
if r.status_code == 404:
return None
module.fail_json(msg=f"API error: {r.status_code} {r.text}")
except requests.RequestException as e:
module.fail_json(msg=f"Connection error: {e}")
def create_resource(module, name, url, token):
"""Create new resource."""
if module.check_mode:
return {"id": "would-create", "name": name, "status": "would-create"}
try:
r = requests.post(
f"{url}/resources",
headers={"Authorization": f"Bearer {token}"},
json={"name": name},
timeout=10,
)
if r.status_code in (200, 201):
return r.json()
module.fail_json(msg=f"Create failed: {r.status_code} {r.text}")
except requests.RequestException as e:
module.fail_json(msg=f"Connection error: {e}")
def delete_resource(module, name, url, token):
"""Delete resource."""
if module.check_mode:
return True
try:
r = requests.delete(
f"{url}/resources/{name}",
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
if r.status_code in (200, 204):
return True
module.fail_json(msg=f"Delete failed: {r.status_code} {r.text}")
except requests.RequestException as e:
module.fail_json(msg=f"Connection error: {e}")
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]),
api_url=dict(type="str", required=True),
api_token=dict(type="str", required=True, no_log=True),
),
supports_check_mode=True,
)
name = module.params["name"]
state = module.params["state"]
url = module.params["api_url"]
token = module.params["api_token"]
current = get_resource(module, name, url, token)
if state == "present":
if current is None:
new_resource = create_resource(module, name, url, token)
module.exit_json(changed=True, resource=new_resource)
else:
module.exit_json(changed=False, resource=current)
elif state == "absent":
if current is None:
module.exit_json(changed=False)
else:
delete_resource(module, name, url, token)
module.exit_json(changed=True)
if __name__ == "__main__":
main()
Common findings this catches
- Module always changed → idempotency missing.
- Check mode does the action → check
module.check_modeflag. - Errors leave partial state → handle cleanup.
- Sensitive arg logged →
no_log: trueon arg spec. - No DOCUMENTATION → ansible-doc fails.
- Missing argument validation → injection.
- Module imports heavy lib → slow load.
When to escalate
- Public module → upstream review.
- Collection inclusion — coordinate.
- Module testing strategy — engineering.
Related prompts
-
Ansible Collections Design Prompt
Build and use Ansible Collections — FQCN, requirements.yml, ansible-galaxy collection install, custom collections.
-
Ansible Molecule Testing Prompt
Test Ansible roles with Molecule — scenarios, drivers (Docker/Podman/Vagrant), verifiers (Ansible/Testinfra), idempotence.
-
Ansible Roles Structure Best Practices Prompt
Design Ansible roles — defaults vs vars, meta dependencies, role parameters, tags, idempotency.