Skip to content
CloudOps
All prompts
AI for Infrastructure as Code Difficulty: Advanced ClaudeChatGPT

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

  1. Follow module contract.
  2. Support check mode.
  3. Document thoroughly.
  4. 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_mode flag.
  • Errors leave partial state → handle cleanup.
  • Sensitive arg loggedno_log: true on 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

Newsletter

Get weekly AI workflows for DevOps engineers

Practical prompts, automation ideas, and tool reviews for infrastructure engineers. One email per week. No spam.