<aside> 🧭

Module 10 · Custom Modules & Plugins

Extending Ansible when nothing existing fits — and, just as importantly, recognising the far more common case where something already does.

🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)

Prerequisite: Modules 01–09. You need the execution model from Module 01 Part B6 and the collection layout from Module 06.

</aside>


Part A · When to write one, and when not to

A1 · The decision tree

<aside> 🔨

The analogy. Think of needing a tool you do not own.

First you check the drawer, because you probably already have one. Then you consider whether an ordinary screwdriver would do the job. Then whether you could borrow one for the afternoon.

Forging your own is the last option, not the first — and once you have made it, you own it forever: sharpening it, storing it, replacing it when it breaks.

Writing a custom module is forging the tool. Sometimes exactly right, and the reason the honest interview answer is "rarely, and deliberately".

</aside>

<aside> 📖

Official docs: Developing modules · Developing plugins · Module best practices

</aside>

flowchart TD
    A["I need something<br>Ansible does not do"] --> B{"Search the module index.<br>Does one exist?"}
    B -->|"Yes"| B1["Use it. Stop here."]
    B -->|"No"| C{"Is it an HTTP API?"}
    C -->|"Yes"| C1["Use the uri module<br>wrapped in a role"]
    C -->|"No"| D{"Is it data transformation<br>rather than an action?"}
    D -->|"Yes"| D1["Write a FILTER plugin<br>far simpler than a module"]
    D -->|"No"| E{"Is it fetching data<br>on the control node?"}
    E -->|"Yes"| E1["Write a LOOKUP plugin"]
    E -->|"No"| F{"Used more than<br>a few times?"}
    F -->|"No"| F1["A guarded command task<br>is honest and cheaper"]
    F -->|"Yes"| G["Write a MODULE"]
    style B1 fill:#D1FAE5,stroke:#059669,stroke-width:2px
    style G fill:#DDD6FE,stroke:#7C3AED,stroke-width:2px

<aside> 🎯

The mature answer to "have you written a custom module?" is not just yes or no. It is: "Rarely, and deliberately. Most of the time the right answer is an existing module, the uri module for an API, or a filter plugin for data shaping. I write a module when we need idempotency and check-mode support against a system with no existing coverage — because those two properties are exactly what a command task cannot give you."

That framing shows judgement. Enthusiasm for writing modules reads as inexperience.

</aside>

A2 · What a module buys you over command

<aside> 👷

The analogy. Think of two builders quoting for a job.

The first says "I'll start Monday" and begins knocking walls down. You find out what he did when you get home.

The second walks the house first and says: "That wall is already the right size, so I'll leave it. This one needs moving. Here's exactly what I'd change — shall I proceed?"

The second builder is a real module. Checking before acting is idempotency; describing the work without doing it is check mode. Those two behaviours are the entire reason a module is worth writing instead of using command.

</aside>

command / shell A real module
Idempotency ❌ You bolt it on with creates or changed_when ✅ Built in — it inspects state first
Check mode ❌ Skips entirely — your dry run is worthless ✅ Reports what would change
Argument validation ❌ None ✅ Types, choices, required, mutually exclusive
Structured return ❌ You parse stdout ✅ JSON with documented keys
Secret handling ❌ Arguments appear in logs no_log=True censors automatically
Documentation ❌ A comment, if you are lucky ansible-doc renders it

<aside> 🔑

Check mode and idempotency are the two that justify the effort. Everything else is convenience. If your team runs --check --diff before production changes — and Module 01 Part D5 argued they should — then a command task is a hole in that safety net, because it skips in check mode and tells you nothing.

</aside>

🎯 Interview questions — When to extend Ansible


Part B · Anatomy of a module

B1 · The complete skeleton