Playbooks, Plays, and Tasks
Relevant source files
The following files were used as context for generating this wiki page:Ansible's execution model is structured around the concepts of Playbooks, Plays, and Tasks. A Playbook is a file that contains one or more Plays. A Play is the core execution unit that maps a set of hosts to a series of tasks. Each Play contains lists of tasks, which are individual units of work executed on the target hosts. This structure allows for organizing complex automation workflows.
The fundamental objects representing these concepts are the Play, Role, Block, Task, and Handler classes. A Play object orchestrates the execution of pre_tasks, roles, tasks, and post_tasks against a specified group of hosts. Tasks can be grouped into Block objects, which also provide error handling capabilities. Handler objects are special tasks that are only executed when notified by other tasks.
Core Components and Relationships
The main components of Ansible's execution structure are hierarchically related. A Play contains lists of Blocks, which in turn contain Tasks. Handlers are a specialized form of Task. Roles are reusable units that package tasks, handlers, and variables, and are compiled into the Play's execution plan.
This class diagram illustrates the inheritance and composition relationships between the key playbook objects.
The Play Object
The Play class is the central object for an execution run. It defines the target hosts and the sequence of operations to be performed.
Sources: lib/ansible/playbook/play.py
Key Attributes
A Play object is defined by several key attributes that are loaded from a playbook file.
| Attribute | Type | Description |
|---|---|---|
hosts | list | A required list of host patterns to target for execution. |
gather_facts | bool | Whether to run the setup module to gather facts about the target hosts. |
roles | list | A list of Role objects to be executed. |
pre_tasks | list | A list of Block objects to be executed before roles. |
tasks | list | The main list of Block objects to be executed after roles. |
post_tasks | list | A list of Block objects to be executed after the main tasks. |
handlers | list | A list of Block objects containing Handler tasks. |
vars_files | list | A list of files from which to load variables. |
strategy | string | The execution strategy plugin to use (e.g., linear, free). Defaults to linear. |
serial | list | Defines how many hosts to run on at a time (rolling updates). |
Sources: lib/ansible/playbook/play.py:46-81
Play Execution Flow
The execution order of a play is determined by the compile() method of the Play class. It assembles the pre_tasks, roles, tasks, and post_tasks into a single list of blocks to be executed. Crucially, it inserts flush_handlers meta-tasks between these sections to ensure that notified handlers are run at the appropriate times.
The following flowchart shows the compilation logic from play.py.
Sources: lib/ansible/playbook/play.py:273-333
The Task Object
A Task is the most basic unit of execution in Ansible. It represents a single call to an Ansible module with a specific set of arguments.
Sources: lib/ansible/playbook/task.py
Key Attributes
| Attribute | Type | Description |
|---|---|---|
action | string | The name of the module to execute. |
args | dict | A dictionary of arguments to pass to the module. |
name | string | A descriptive name for the task, used in logging and output. |
when | string/list | A conditional expression that must be true for the task to run. |
tags | list | A list of tags applied to the task for selective execution. |
register | string | The name of a variable to store the task's result in. |
delegate_to | string | The host on which to execute the task, overriding the play's target host. |
Sources: lib/ansible/playbook/task.py:42-108
A special type of task is the meta task, which influences Ansible's execution rather than performing an action on a remote host. Examples include flush_handlers and refresh_inventory.
Sources: lib/ansible/modules/meta.py:20-53
The Block Object
The Block class is a container for a list of tasks. It also provides a mechanism for error handling, similar to try...except...finally in many programming languages.
Sources: lib/ansible/playbook/block.py
Structure
A Block has three main parts:
block: The main list of tasks to be executed.rescue: A list of tasks to execute if any task in theblocksection fails.always: A list of tasks that will always be executed, regardless of the success or failure of theblockandrescuesections.
The Play class uses load_list_of_blocks to process its tasks, pre_tasks, post_tasks, and handlers lists, ensuring that any bare tasks are implicitly wrapped in a Block object.
Sources: lib/ansible/playbook/play.py:192-228, lib/ansible/playbook/block.py:33-40
The Handler Object
A Handler is a special type of Task that is only run when "notified" by another task. Handlers are typically used for service restarts or other actions that should only occur if a change was made.
Sources: lib/ansible/playbook/handler.py
Key Characteristics
- Inheritance: The
Handlerclass inherits from theTaskclass, so it shares all the same attributes. - Notification: A task notifies a handler by name. If multiple tasks notify the same handler, it will still only run once.
- Execution Point: Handlers are executed at the end of each play section, triggered by the
flush_handlersmeta task automatically inserted by thePlay.compile()method. listen: Handlers can "listen" for arbitrary notification topics, allowing for more flexible notification schemes beyond just the handler's name.
Sources: lib/ansible/playbook/handler.py:32-34, lib/ansible/playbook/play.py:273-333
Conclusion
The playbook structure, centered around Play, Task, Block, and Handler objects, provides a powerful and flexible framework for defining automation workflows. A Play defines the "what" and "where" by mapping tasks to hosts. The compilation process ensures a predictable execution order of pre_tasks, roles, tasks, and post_tasks, with handlers being flushed at the end of each major section. This hierarchical and modular design is fundamental to Ansible's ability to manage complex infrastructure configurations.