ansible/ansible
Playbook and play
A playbook is a list of plays. A play is a unit of "do X to Y under conditions Z". Both are primitive YAML-loadable objects in lib/ansible/playbook/.
Files
| File | Class | Lines |
|---|---|---|
lib/ansible/playbook/__init__.py |
Playbook |
116 |
lib/ansible/playbook/play.py |
Play |
483 |
lib/ansible/playbook/playbook_include.py |
PlaybookInclude |
130 |
lib/ansible/playbook/base.py |
Base (parent class) |
800 |
lib/ansible/playbook/play_context.py |
PlayContext |
360 |
Playbook
Playbook is the simplest of the three. Its job is to represent a YAML file containing a list of plays:
class Playbook:
def __init__(self, loader):
self._entries = [] # list of Play (and PlaybookInclude)
self._basedir = ...
self._loader = loader
@staticmethod
def load(file_name, variable_manager=None, loader=None):
pb = Playbook(loader=loader)
pb._load_playbook_data(file_name, variable_manager)
return pb_load_playbook_data opens the file via DataLoader, asserts it's a list, and iterates. For each entry:
- If it has
import_playbook:, instantiate aPlaybookIncludeand splice in the included playbook's plays. - Otherwise, instantiate a
Play.load(entry, ...)and append.
The loader's basedir is set per-playbook so relative paths in includes resolve correctly.
Play
Play is the workhorse. A play is a YAML dict with a hosts: pattern and optional tasks:, roles:, pre_tasks:, post_tasks:, handlers:, vars:, vars_files:, plus dozens of other keywords.
Play extends Base, plus mixins Taggable, CollectionSearch. Its FieldAttribute declarations enumerate every supported YAML key:
hosts = FieldAttribute(isa='list', required=True, ...)
vars = FieldAttribute(isa='dict', priority=100, default=dict)
gather_facts = FieldAttribute(isa='bool', default=...)
gather_subset = FieldAttribute(isa='list', default=...)
gather_timeout = FieldAttribute(isa='int', default=...)
serial = FieldAttribute(isa='list', default=lambda: [], priority=1, ...)
strategy = FieldAttribute(isa='string', default=C.DEFAULT_STRATEGY)
order = FieldAttribute(isa='string', choices=['inventory', 'reverse_inventory', 'sorted', 'reverse_sorted', 'shuffle'])
roles = FieldAttribute(isa='list', default=list, priority=90, ...)
tasks = FieldAttribute(isa='list', default=list, ...)
pre_tasks = FieldAttribute(isa='list', default=list, ...)
post_tasks = FieldAttribute(isa='list', default=list, ...)
handlers = FieldAttribute(isa='list', default=list, ...)
vars_files = FieldAttribute(isa='list', default=list, ...)
become = FieldAttribute(isa='bool', ...)
become_user = FieldAttribute(isa='string', ...)
become_method = FieldAttribute(isa='string', default=...)
connection = FieldAttribute(isa='string', ...)
remote_user = FieldAttribute(isa='string', ...)
no_log = FieldAttribute(isa='bool', default=False)
force_handlers = FieldAttribute(isa='bool', default=...)
max_fail_percentage = FieldAttribute(isa='percent', default=0)
any_errors_fatal = FieldAttribute(isa='bool', default=...)
fact_path = FieldAttribute(isa='string', ...)Each FieldAttribute has a type (isa), a default, optional choices, and a priority that affects load ordering when fields depend on each other.
Compilation
Play.compile() produces the flat task list the executor walks. The order is:
metatask:flush_handlersifforce_handlers.- Tasks compiled from
pre_tasks:. meta: flush_handlers.- Tasks compiled from
roles:(each role'stasks/main.yml, in dependency order). - Tasks compiled from
tasks:. meta: flush_handlers.- Tasks compiled from
post_tasks:. - Final
meta: flush_handlers. - Handlers from
handlers:(only run on notify).
This is what produces the standard "PRE → roles → MAIN → POST → HANDLERS" execution sweep.
Roles vs. tasks
When you list roles in a play, role-level tasks become regular tasks at compile time. The role importer is what makes that work; see Role and handler. Pre-flush, the executor doesn't see roles — it sees a flat task list that already has role tasks merged in.
Hosts patterns and serial
The hosts: field is a host pattern — a string or list of patterns. Play._get_hosts() (called by PlaybookExecutor) resolves the pattern via InventoryManager.list_hosts(), applies any --limit, and returns the working host list.
serial: controls batching. With serial: 5, the play runs against the first 5 hosts, then the next 5, etc. With a list (serial: [1, 5, "30%"]) it does a "canary then ramp" pattern — 1 host, then 5, then 30% of remaining. Implementation in lib/ansible/executor/playbook_executor.py:PlaybookExecutor.run().
gather_facts
gather_facts: true (default in older versions; per-play config in newer) prepends a gather_facts task to the compiled task list. The action plugin dispatches to the configured fact-gathering modules (default: setup).
PlaybookInclude
import_playbook: (the only static-include form for plays — include_playbook was removed) loads another playbook and splices its plays into the current playbook at parse time.
class PlaybookInclude:
@staticmethod
def load(data, basedir, variable_manager, loader):
if not 'import_playbook' in data and not (any of the legacy spelling):
return None
# Load the referenced file
pb = Playbook.load(playbook_path, ...)
# Apply when:, vars:, tags: from the include
return pbPlaybookInclude wraps the included playbook's plays so they inherit vars:, tags:, and when: from the include statement. The conditional and tag system means a single import-playbook line can be skipped or modified per-run.
PlayContext
PlayContext is a derived per-(host, task) object that carries the resolved connection settings: who to connect as, on which port, with what password, with which become method, with which environment variables. It's not serialized to YAML — it's computed at runtime by combining play options, task options, host vars, and CLI flags.
class PlayContext:
connection: str
remote_user: str
port: int
become: bool
become_method: str
become_user: str
timeout: int
private_key_file: str
password: str
become_password: str
diff: bool
check_mode: bool
...Built fresh by TaskQueueManager for each (host, task) pair. The fields are passed into the connection plugin via its constructor and the action plugin via self._play_context.
Lifecycle
graph TD
YAML[playbook.yml] --> LOAD[Playbook.load]
LOAD --> PARSE[For each entry: Play.load or PlaybookInclude.load]
PARSE --> ENTRIES[Playbook._entries]
ENTRIES --> EXEC[PlaybookExecutor.run]
EXEC --> COMPILE[Play.compile]
COMPILE --> TASKS[Flat list of Task]
TASKS --> TQM[TQM.run]Integration points
- Imported by:
lib/ansible/cli/playbook.py,lib/ansible/cli/console.py,lib/ansible/cli/pull.py,lib/ansible/executor/playbook_executor.py. - Imports:
lib/ansible/playbook/base.py,lib/ansible/playbook/block.py,lib/ansible/playbook/role/__init__.py,lib/ansible/playbook/task.py,lib/ansible/parsing/dataloader.py.
Entry points for modification
- A new top-level play keyword — declare a new
FieldAttributeonPlay. Keepversion_addedaccurate. The keyword description inlib/ansible/keyword_desc.ymlpowersansible-doc --keyword. - Changing pre/post/role ordering —
Play.compile(). Sensitive; users assume specific execution order. - A new include style for plays — extend
PlaybookInclude.load. Note that legacyinclude_playbookwas deliberately removed.
Cross-links
- Task and block — what each play contains.
- Role and handler — how roles get pulled in.
- Executor — what consumes the compiled task list.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.