ansible/ansible
Patterns and conventions
The de-facto rules for new code in ansible-core. Most of these are spelled out in AGENTS.md at the repo root; the rest are visible across the existing tree.
Code style
- Line length: 160. Not 80. The relaxed limit accommodates long imports and SQL-like Jinja expressions in tests.
from __future__ import annotationsat the top of every Python file. This converts type hints to strings at runtime, allowing forward references and avoiding circular-import issues.- No trailing whitespace. Sanity will reject it. Always strip when editing existing files.
- No obvious comments.
# return the resultadds no information; skip it. - Don't document module parameters in docstrings. Migrate to type hints. The
argument_specand theDOCUMENTATIONYAML block are the source of truth for parameter docs. - Sentence-case error messages.
display.error("Could not parse playbook")not"could not parse playbook".
File headers
Every controller-side file starts with the GPLv3 header (or, in module_utils/, BSD-2-Clause). Look at the top of any existing file for the canonical pattern, e.g., lib/ansible/cli/playbook.py:
#!/usr/bin/env python
# Copyright: (c) 2012, ...
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import annotationsThe # PYTHON_ARGCOMPLETE_OK marker enables shell tab-completion via argcomplete.
Module file structure
Modules in lib/ansible/modules/ have a strict ordering enforced by sanity:
#!/usr/bin/python
# Copyright + license header
from __future__ import annotations
DOCUMENTATION = r'''
---
module: foo
short_description: ...
description:
- ...
options:
...
'''
EXAMPLES = r'''
- name: do the thing
foo:
bar: baz
'''
RETURN = r'''
result:
description: ...
returned: always
type: dict
'''
# Imports come AFTER the YAML strings — these are AST-parsed.
from ansible.module_utils.basic import AnsibleModule
...
def main():
module = AnsibleModule(argument_spec=dict(...))
...
if __name__ == '__main__':
main()The DOCUMENTATION/EXAMPLES/RETURN constants are extracted at build time by AST/token parsing — they cannot be dynamically generated, and the order matters.
Display, not print
Use the singleton Display for all controller output:
from ansible.utils.display import Display
display = Display()
display.display("Hello", color=C.COLOR_OK)
display.warning("Be careful")
display.error("Boom", wrap_text=True)
display.deprecated("Old API", version="2.22")
display.vvv("verbose detail")lib/ansible/utils/display.py serializes output across worker processes and respects color/verbosity settings.
Errors
The custom exception hierarchy lives in lib/ansible/errors/__init__.py:
AnsibleError— base class; carriesmessage,obj(the YAML node where the error originated), and_help_text.AnsibleParserError— YAML/playbook parsing failures.AnsibleAssertionError— invariant violations (use sparingly).AnsibleOptionsError— bad CLI arguments.AnsibleUndefinedVariable—{{ undefined }}referenced.AnsibleTemplateErrorand friends — template errors with location info.AnsibleConnectionFailure,AnsibleAuthenticationFailure— connection-layer failures.
Always raise these, not Exception or bare strings. The TQM and callback layer pattern-match on these types to display structured failures.
Plugin lifecycle
Plugin classes follow these conventions:
- Inherit from the relevant base under
lib/ansible/plugins/<type>/__init__.py(e.g.,ActionBase,ConnectionBase,LookupBase,BecomeBase,StrategyBase). - Class is named after the plugin; instances are constructed by
lib/ansible/plugins/loader.py:PluginLoader.get(). - Documentation lives in a top-of-file
DOCUMENTATION = """ ... """block; the YAML schema mirrors module docs. - Configuration options use
vars/env/inideclarations in theDOCUMENTATIONblock; the loader auto-wires them up viaOptionobjects.
Datatag awareness
Code that handles user-supplied YAML data should be tag-aware. The relevant primitives are in lib/ansible/module_utils/_internal/_datatag.py:
AnsibleTagHelper.tag(value, *tags)— attach tags.AnsibleTagHelper.untag(value)— strip tags (rarely correct).AnsibleTaggedObject— base class for tagged values.
Tags include Origin (where it came from in source), TrustedAsTemplate (eligible for {{ }} rendering), VaultedValue (Vault-encrypted at rest). Most of the time you don't need to think about tags — they propagate through string operations — but in templating-related code you must respect TrustedAsTemplate rather than blindly running everything through Jinja.
Imports and dependencies
Per AGENTS.md:
- Prefer Python stdlib over external dependencies.
- Use existing code from within Ansible. Don't fork helpers.
lib/ansible/modules/can only import fromlib/ansible/module_utils/. Modules are bundled and shipped to remote hosts; they cannot import from anywhere else.lib/ansible/module_utils/cannot import from outside itself. Anything in here gets shipped along with modules, so it must be self-sufficient.
This is enforced by the import sanity test.
Public vs. internal
Anything under lib/ansible/_internal/ is private to ansible-core and may change without notice. The sanity tests block external imports of _internal modules.
The original templating code at lib/ansible/template/__init__.py is the public legacy surface; new code uses the internal lib/ansible/_internal/_templating/_engine.py:TemplateEngine instead.
Deprecation
Use Display.deprecated (controller) or AnsibleModule.deprecate (modules) with a version= argument that's the current version + 3 (4-release deprecation cycle). To find the current version, read lib/ansible/release.py:__version__.
display.deprecated(
"The old foo() function is deprecated, use new_foo() instead.",
version="2.22",
)Mark deprecated public APIs with @deprecated decorators where available, and add a removed_in field if there's a hard removal target.
Testing
See Testing. The summary:
- Unit tests should be functional, not heavily mock-dependent.
- Integration tests are required for almost all plugin changes — they exercise the public API end-to-end.
- Tests must exercise the actual changed code, not just add random coverage.
Changelog fragments
Every PR with behavior change needs a YAML fragment under changelogs/fragments/. See Development workflow.
Cross-links
- Development workflow for the day-to-day cycle.
- Testing for the test taxonomy.
- Reference → Configuration for the config option model.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.