Validating Ansible Module Arguments With argument_spec and AI
Use argument_spec to validate Ansible module inputs properly, with AI drafting the spec: types, required_if, mutually_exclusive, no_log, and a verified contract.
- #ansible
- #ai
- #modules
- #validation
- #python
Every custom Ansible module starts with a decision most people make carelessly: how it validates its inputs. Get it right and the module rejects bad parameters with a clear message before doing any work. Get it wrong and it half-executes on garbage input, leaks a password into the logs, or accepts a combination of arguments that makes no sense and produces a broken result. The whole of Ansible’s input validation lives in one structure — the argument_spec you hand to AnsibleModule — and it’s worth treating that spec as the module’s contract rather than a formality.
This is a great spot for AI assistance because the argument_spec syntax is fiddly and easy to get subtly wrong, but the rules you want it to enforce are things only you know. So I describe the contract in plain language, let the model translate it into a spec, and then verify the spec actually rejects what it should.
The spec is the contract
AnsibleModule takes an argument_spec dict that declares every parameter: its type, whether it’s required, its default, and its allowed values. Ansible validates incoming task arguments against this before your code runs, which means a well-built spec turns a class of runtime failures into clean, early errors.
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
port=dict(type='int', default=443),
password=dict(type='str', required=False, no_log=True),
mode=dict(type='str', choices=['fast', 'safe']),
),
supports_check_mode=True,
)
# module.params is now validated and ready
Three things in that block do real work. type coerces and validates, so port arrives as an int or the task fails. choices rejects anything off the list. And no_log=True on password is the one that prevents a security incident — it keeps the value out of logs and verbose output. Forgetting no_log on a secret parameter is the most common and most damaging mistake in a homegrown module.
Cross-argument rules: where validation gets real
Single-parameter checks are the easy part. The interesting validation is across parameters — rules like “if state is present, name is required” or “you can’t set both src and content.” AnsibleModule has built-in support for these, and they’re far better than hand-rolled if checks because they produce consistent, clear errors:
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
name=dict(type='str'),
src=dict(type='str'),
content=dict(type='str'),
token=dict(type='str', no_log=True),
),
required_if=[
('state', 'present', ('name',)),
],
mutually_exclusive=[
('src', 'content'),
],
required_one_of=[
('src', 'content'),
],
supports_check_mode=True,
)
required_if, mutually_exclusive, required_one_of, and required_together cover the vast majority of real cross-argument logic. Pushing these into the spec instead of into your function body means Ansible enforces them uniformly and your module body can assume it received a coherent set of arguments.
The AI prompt that writes the spec
I describe the rules; the model writes the spec. The trick is to be exhaustive about the cross-argument constraints, because those are what the AI can’t infer:
Write the
AnsibleModuleargument_spec for a module that manages a config entry. Parameters:name(string),state(present/absent, default present),value(string),secret_value(string, sensitive),expires(int, optional). Rules: when state is present, exactly one ofvalueorsecret_valueis required and they’re mutually exclusive;secret_valuemust never appear in logs;expiresonly makes sense with state present. Userequired_if,mutually_exclusive,required_one_of, andno_logappropriately, and setsupports_check_mode=True. Explain each constraint in a comment.
What I get back is a spec that’s structurally correct. What I verify myself is whether no_log is on every sensitive field and whether the cross-argument rules actually match the contract I described — because a draft that’s 90% right on validation is still a module that accepts a bad input combination 10% of the time.
Verifying the contract holds
A spec you didn’t test is a spec you don’t trust. The fast way to verify is to throw deliberately bad arguments at the module and confirm it rejects them cleanly:
# Missing required name with state=present should fail clearly
ansible localhost -m my_config -a "state=present value=x"
# Both value and secret_value should be rejected as mutually exclusive
ansible localhost -m my_config -a "state=present name=n value=a secret_value=b"
# Confirm the secret never shows up, even at -vvv
ansible localhost -vvv -m my_config -a "state=present name=n secret_value=hunter2" 2>&1 | grep -i hunter2
fatal: [localhost]: FAILED! => {"msg": "state is present but all of the following are missing: ... name"}
fatal: [localhost]: FAILED! => {"msg": "parameters are mutually exclusive: src|content"}
That last grep is the security check I never skip. If hunter2 shows up anywhere in the verbose output, the no_log is missing or misplaced, and the module is leaking secrets into every run’s logs. The spec’s job is to make that impossible, and the only way to know it works is to try to break it.
Pro Tip: Add these “bad input” invocations as unit tests with ansible-test units so the contract is enforced in CI. A future refactor that loosens validation should fail the build, not slip through to production.
Why this beats hand-rolled checks
You could enforce all of this with if statements at the top of main(). People do, and it’s worse in every way: the error messages are inconsistent, the secret-scrubbing is manual and forgettable, and the rules are buried in code instead of declared in one readable structure. The argument_spec approach makes the contract visible, makes Ansible enforce it uniformly, and — with no_log — handles the one mistake you genuinely cannot afford. Let AI draft the spec from your rules, then verify it rejects what it should and hides what it must.
For the broader module-writing workflow, see writing custom Ansible modules in Python with AI and the AI for Ansible category. To turn your validation rules into a reusable AI prompt, browse the Ansible prompts.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.