DocSmith for Ansible
Licensing
- Primary license: GPL-3.0-or-later
Miscellaneous
- Funding: Buy us a coffee
- Maintainer: foundata
- Status: Stable
README
Automating role documentation (using argument_specs.yml)
DocSmith is a documentation generator. It reads a role’s
meta/argument_specs.yml
and produces upātoādate variable descriptions for the README.md as well as
inline comment blocks for defaults/main.yml (or other role entry-point files).
It works with roles in both
standāalone form
and within
collections.
Demo
Roles using DocSmith
- Ansible role:
foundata.acmesh.run: - Ansible role:
foundata.sshd.run:
Screenshots
Features
- Uses the
argument_specs.ymlfrom Ansible’s built-in role argument validation as the source for documentation in READMEs and entry-point files. - Checks argument specs for structural errors and inconsistencies with
entry-point
defaults/. See validation for checks and severity levels. - Provides read-only checks and exit codes for CI/CD pipelines and pre-commit hooks.
- Supports Markdown and reStructuredText.
- Converts
Ansible markup,
such as
C(...),O(...),V(...)andM(...), in descriptions to the target format.
Installation
DocSmith needs Python ā„ v3.11. It is available on PyPI and can be installed with the package manager of your choice.
Using uv
(recommended):
uv tool install ansible-docsmith
Alternatively, choose one of the following commands:
pip install ansible-docsmith
pipx install ansible-docsmith
The minimum Python version is 3.11 (rather than the newer 3.12 baseline used for most of our projects) so DocSmith runs on Debian 12 “Bookworm”.
Usage
Preparations
Create
meta/argument_specs.ymlfor Ansible’s role argument validation if it does not exist. Adddescription:to your variables to make the generated documentation useful. A complete specification also improves argument validation.Add the mandatory
MAINmarkers to your role’s existingREADME.mdwhere the variable descriptions should appear. Eachansible-docsmith generaterun replaces all content between them:<!-- ANSIBLE DOCSMITH MAIN START --> <!-- ANSIBLE DOCSMITH MAIN END -->Optionally, add
TOCmarkers inside a hand-written table of contents (ToC). These generate list entries for only the DocSmith-managed variable documentation:<!-- ANSIBLE DOCSMITH TOC START --> <!-- ANSIBLE DOCSMITH TOC END -->Alternatively, add
TOC-FULLmarkers to generate a complete ToC of all README headings, including hand-written ones:<!-- ANSIBLE DOCSMITH TOC-FULL START --> <!-- ANSIBLE DOCSMITH TOC-FULL END -->Headings with an explicit anchor (like
## Usage<a id="usage"></a>) are linked exactly; for other headings, the anchor is derived from the heading text andvalidateemits a notice, as the derivation cannot be guaranteed to match your rendering platform for exotic titles.
The entry-point variable files in your role’s defaults/ directory need no
additional preparation. DocSmith adds or replaces formatted inline comment
blocks above the variables defined there.
The marker contract in short:
- All content between a START and END marker pair is owned by DocSmith and
gets replaced on every
generaterun. Everything outside the markers is never touched. - A lone marker is always an error, no matter the type: a
STARTwithout itsEND(or vice versa) fails validation instead of guessing where the managed section ends. This applies toMAIN,TOC,TOC-FULLand the role-named markers in collection READMEs alike. - A missing README is fine:
generatecreates one from a basic skeleton, markers included. However, an existing README without the mandatoryMAINmarkers is a hard validation error, so DocSmith cannot accidentally overwrite hand-written content in a README that was never prepared for it.
Example files:
- Markdown:
README.md - reStructuredText:
README.rst(difference to Markdown:..comments,.. contents:: **Table of Contents**directive)
Generate or update documentation
Basic usage:
## Safely preview changes without writing to files. No modifications are made.
ansible-docsmith generate /path/to/role --dry-run
## Check whether the documentation is up to date without writing files:
## exit code 1 (and a diff) if a run would change anything, 0 otherwise.
## Useful for CI/CD pipelines and pre-commit hooks.
ansible-docsmith generate /path/to/role --check
## Generate / update README.md and comments in entry-point files (like defaults/main.yml)
ansible-docsmith generate /path/to/role
## Show help
ansible-docsmith --help
ansible-docsmith generate --help
Advanced parameters:
## Generate / update only the README.md, skip comments for variables in
## entry-point files (like defaults/main.yml).
ansible-docsmith generate /path/to/role --no-defaults
## Generate / update only the comments in entry-point files (like defaults/main.yml),
## skip README.md
ansible-docsmith generate /path/to/role --no-readme
## Do not document nested options ("dict attributes") in the comments of
## entry-point files (like defaults/main.yml)
ansible-docsmith generate /path/to/role --no-defaults-comments-nested
## Verbose output for debugging
ansible-docsmith generate /path/to/role --verbose
Collections
generate and validate also accept a collection path. All roles found via
roles/*/meta/argument_specs.yml are then processed like single roles.
Additionally, DocSmith maintains role-named marker sections in the collection’s
README.md (or README.rst), so the collection README can reference the role
documentation without manual upkeep:
#### My role: foo
<!-- ANSIBLE DOCSMITH TOC foo START -->
<!-- ANSIBLE DOCSMITH TOC foo END -->
#### My role: bar
<!-- ANSIBLE DOCSMITH TOC-FULL bar START -->
<!-- ANSIBLE DOCSMITH TOC-FULL bar END -->
<!-- ANSIBLE DOCSMITH MAIN bar START -->
<!-- ANSIBLE DOCSMITH MAIN bar END -->
TOC <role>lists the role’s variable documentation,TOC-FULL <role>lists all headings of the role’s README. Both link intoroles/<role>/README.*using relative paths.MAIN <role>embeds the role’s complete variable documentation directly in the collection README. All anchors get a<role>-prefix so several embedded roles cannot collide on variable names. If aTOC <role>section exists in the same (Markdown) document, it links to the embedded documentation instead of the role’s README.- Role-named sections are opt-in per role: roles without markers are simply not
referenced in the collection README (
validateemits a notice listing them). Markers referencing an unknown role produce a warning.
ansible-docsmith generate /path/to/collection
ansible-docsmith validate /path/to/collection
ansible-docsmith generate /path/to/collection --check
Validate argument_specs.yml and /defaults
Validate the argument specifications and their consistency with role entry-point
files in defaults/:
ansible-docsmith validate /path/to/role
Checks include:
- Errors for variables present in
defaults/but missing fromargument_specs.yml, or variables with adefault:in the specification but missing from the entry-point files. - Warnings for unknown keys in
argument_specs.ymland invalid Ansible markup in descriptions, such asM()without a fully qualified collection name (FQCN). - Notices for non-required variables listed in the specification but absent from
an otherwise populated entry-point defaults file, and names suggesting secrets
(such as
*_passwordor*_token) withoutno_log: true.
Use --strict to make warnings fail validation with exit code 1, for example
in CI/CD pipelines or pre-commit hooks. Notices do not fail validation.
ansible-docsmith validate /path/to/role --strict
To validate selected parts of a role:
## Skip the README checks (markers and ToC). Useful when only maintaining
## comments in entry-point files.
ansible-docsmith validate /path/to/role --no-readme
## Skip the argument_specs.yml checks (consistency, unknown keys, ...).
## The file must still be parseable YAML.
ansible-docsmith validate /path/to/role --no-argument-specs
## Show help
ansible-docsmith --help
ansible-docsmith validate --help
## Verbose output for debugging
ansible-docsmith validate /path/to/role --verbose
Exit codes
All commands use conventional exit codes, so DocSmith can be wired into scripts, CI/CD pipelines and pre-commit hooks without output parsing:
| Exit code | Meaning |
|---|---|
0 | Success. Warnings and notices alone do not fail a run (unless --strict is used). generate --check returns 0 when the documentation is up to date. |
1 | Validation or processing error (like missing MAIN markers, inconsistencies between argument_specs.yml and defaults/). Also: warnings when validate --strict is used, and pending changes when generate --check is used. |
2 | Command line usage error (unknown option, non-existing path). |
Notices are informational and never affect the exit code. A typical gate:
ansible-docsmith validate /path/to/role --strict && \
ansible-docsmith generate /path/to/role --check
Custom templates
You can customize the generated output by providing your own
Jinja2 template. The
rendered content will be inserted between the ANSIBLE DOCSMITH MAIN START and
END markers in the role’s README. Name the file *.md.j2 for Markdown or
*.rst.j2 for reStructuredText, matching the README format of the role.
## Use a custom template for README generation
ansible-docsmith generate /path/to/role --template-readme /path/to/custom-template.md.j2
## Combined with other options
ansible-docsmith generate /path/to/role --template-readme ./templates/my-readme.md.j2 --dry-run
Template files must use the .j2 extension (for example, simple-readme.md.j2)
and follow Jinja2 syntax. This Markdown example documents top-level options from
every entry point. It uses the built-in anchor convention so generated ToC
entries and option links can resolve:
{% set multiple_entry_points = specs | length > 1 %}
{% for entry_point, entry_spec in specs.items() %}
{% set short_anchors = (not multiple_entry_points) or entry_point == "main" %}
{% set prefix = anchor_ns ~ ("" if short_anchors else entry_point ~ "-") %}
### Role variables: `{{ entry_point }}` {#{{ prefix }}variables}
{% if entry_spec.options %}
{% for var_name, var_spec in entry_spec.options.items() %}
#### `{{ var_name }}` {#{{ prefix }}variable-{{ var_name }}}
Type: `{{ var_spec.type }}`
{{ var_spec.description | format_description }}
{% endfor %}
{% else %}
This entry point has no configurable variables.
{% endif %}
{% endfor %}
Copy the built-in
readme/default.md.j2
template for a starting point that also includes nested options, defaults and
other option metadata, with conditional sections.
Available template variables (the context contract):
| Variable | Type | Description |
|---|---|---|
specs | dict | All entry points in spec file order: entry point name ā normalized spec. This is the recommended way to render complete documentation (the built-in templates iterate it). |
role_name | str | Name of the Ansible role (directory name). |
role_path | Path | Path to the role directory. |
entry_points | list[str] | All entry-point names, in spec file order. |
anchor_ns | str | Anchor namespace to prepend to every anchor and internal link the template generates. Empty for role READMEs; <role>- when the content is embedded into a collection README via MAIN <role> markers. Use it like the built-in templates do, or embedded content of several roles may collide. |
primary_entry_point | str | Backwards compatibility: name of the first entry point. |
primary_spec | dict | Backwards compatibility: spec of the first entry point (keys: short_description, description, author, version_added, options). |
options | dict | Backwards compatibility: variables of the first entry point only. |
has_options | bool | Backwards compatibility: whether the first entry point defines variables. |
Each entry in an options dictionary maps a variable name to its normalized
specification with the keys type, required, default, description,
choices, elements, options (nested sub-options, same structure),
version_added, no_log and aliases.
Available Jinja2 filters (signatures show the optional arguments):
| Filter | Description |
|---|---|
format_description(value) | Formats a description (string or list of paragraphs) for regular display, including Ansible markup conversion. |
format_table_description(value, variable_name=None, max_length=250, anchor_prefix="variable-") | Formats a description for a table cell: markup conversion, HTML stripping, single-line folding and truncation with a [ā¦] link to #<anchor_prefix><variable_name>. Set max_length=0 to disable truncation. |
format_default(value, table=False) | Formats a default value as inline code (N/A for None). Pass true inside Markdown table cells so pipes get escaped. |
code_escape(value, table=False) | Renders a value as inline code, handling backticks correctly. Pass true inside Markdown table cells. |
ansible_escape(value) | Escapes Ansible Jinja2 syntax ({{ }}) so it renders literally (Markdown only). |
csv_escape(value) | Escapes double quotes for csv-table cells (reStructuredText templates only). |
Stability: the variables and filters documented above are the supported
contract and only change with a major release (deprecated values like options
may then disappear). Anything else you might discover by reading DocSmith’s
internals, including undocumented context values, filter internals and module
layout, can change in any release.
Templates can also generate playbook examples and author information between the
markers. The following example uses the compatibility context to document only
the first entry point as a task snippet for a playbook. It renders
JSON-compatible defaults with Jinja2’s tojson filter, whose output is also
valid YAML, and leaves variables without a non-null default commented out.
Supply values for required variables before running the example:
### Example playbook
```yaml
- ansible.builtin.include_role:
name: "{{ role_name }}"
tasks_from: "{{ primary_entry_point }}"
defaults_from: "{{ primary_entry_point }}"
{% if options.values() | rejectattr('default', 'none') | list %}
vars:
{% endif %}
{% for var_name, var_spec in options.items() %}
{% if var_spec.default is not none %}
{{ var_name }}: {{ var_spec.default | tojson }}
{% else %}
# {{ var_name }}: # {{ var_spec.description | format_description | replace('\n', ' ') }}
{% endif %}
{% endfor %}
```
### Author information
{% if primary_spec.author %}
{% for author in primary_spec.author %}
- {{ author }}
{% endfor %}
{% endif %}
Licensing, copyright
Copyright (c) 2025, 2026, foundata GmbH (https://foundata.com)
This project is licensed under the GNU General Public License v3.0 or later
(SPDX-License-Identifier: GPL-3.0-or-later), see
LICENSES/GPL-3.0-or-later.txt for the full
text.
The REUSE.toml file provides detailed licensing and copyright
information in a human- and machine-readable format. This includes parts that
may be subject to different licensing or usage terms, such as third-party
components. The repository conforms to the
REUSE specification. You can use
reuse spdx to create
a
SPDX software bill of materials (SBOM).
Trademarks
Third-party trademarks used in this repository:
- AnsibleĀ®, FedoraĀ® and Red HatĀ® are trademarks of Red Hat, Inc., registered in the United States and other countries.
- DebianĀ® is a trademark of Software in the Public Interest, Inc., registered in Germany and the United States.
- UbuntuĀ® is a trademark of Canonical Ltd., registered in Germany, the European Union and the United States.
Their use here is purely descriptive and does not imply any affiliation with or endorsement by the trademark holders.
Own and licensed trademarks used in this repository:
- foundataĀ® is a trademark of IPAM GmbH, registered in Germany and the European Union, licensed to foundata GmbH.
Author information
This project was created and is maintained by foundata. If you like it, you might buy us a coffee.
The Ansible DocSmith project is not associated with Red Hat nor the Ansible project.





