Open-Source Wikis

/

Ansible

/

Primitives

/

Task and block

ansible/ansible

Task and block

A task is the leaf node of the playbook tree: an action plus its arguments and modifiers. A block is a grouping of tasks that share error-handling, conditionals, and other policies.

Files

File Class Lines
lib/ansible/playbook/task.py Task 619
lib/ansible/playbook/block.py Block 411
lib/ansible/playbook/task_include.py TaskInclude 152
lib/ansible/playbook/conditional.py Conditional (mixin) 35
lib/ansible/playbook/loop_control.py LoopControl 70
lib/ansible/playbook/handler.py Handler 75
lib/ansible/playbook/handler_task_include.py HandlerTaskInclude 35

Task

A Task represents a single YAML dict like:

- name: install nginx
  ansible.builtin.package:
    name: nginx
    state: present
  when: ansible_distribution == 'Ubuntu'
  become: true
  tags:
    - install

The Task class extends Base plus the mixins Conditional, Taggable, CollectionSearch, Notifiable, Delegatable. Each YAML key maps to a FieldAttribute:

class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatable):
    name = FieldAttribute(isa='string', default='')
    action = FieldAttribute(isa='string')
    args = FieldAttribute(isa='dict', default=dict)
    register = FieldAttribute(isa='string')
    loop = FieldAttribute(isa='list')
    loop_control = FieldAttribute(class_type=LoopControl, default=LoopControl)
    until = FieldAttribute(isa='list')
    retries = FieldAttribute(isa='int', default=3)
    delay = FieldAttribute(isa='int', default=5)
    failed_when = FieldAttribute(isa='list', default=list)
    changed_when = FieldAttribute(isa='list', default=list)
    notify = FieldAttribute(isa='list')
    when = FieldAttribute(isa='list', default=list)
    tags = FieldAttribute(isa='list', default=list)
    no_log = FieldAttribute(isa='bool')
    poll = FieldAttribute(isa='int', default=...)
    async_val = FieldAttribute(isa='int', default=0)
    timeout = FieldAttribute(isa='int', default=...)
    delegate_to = FieldAttribute(isa='string')
    delegate_facts = FieldAttribute(isa='bool', default=False)
    run_once = FieldAttribute(isa='bool', default=False)
    ignore_errors = FieldAttribute(isa='bool', default=False)
    ignore_unreachable = FieldAttribute(isa='bool', default=False)
    any_errors_fatal = FieldAttribute(isa='bool', default=False)
    check_mode = FieldAttribute(isa='bool')
    diff = FieldAttribute(isa='bool')
    debugger = FieldAttribute(isa='string')
    throttle = FieldAttribute(isa='int', default=0)

Plus become, become_user, become_method, connection, port, remote_user, vars from inherited mixins and Base.

Action resolution

When a task is loaded, lib/ansible/parsing/mod_args.py:ModuleArgsParser figures out which YAML key represents the action call:

# Style 1: explicit module name as key
- ansible.builtin.copy:
    src: foo
    dest: /tmp/foo

# Style 2: action keyword
- action: ansible.builtin.copy src=foo dest=/tmp/foo

# Style 3: name + module name as key (most common)
- name: copy the file
  ansible.builtin.copy:
    src: foo
    dest: /tmp/foo

ModuleArgsParser.parse() returns (action, args, delegate_to). The action is then resolved through the plugin loader at execution time.

Templating gates

Some attributes template at parse time, others at execution time, and a few are special. Conditionals (when/failed_when/changed_when) template specially: they're treated as bare Jinja expressions (not {{ }}-wrapped strings) and coerced to bool. The Conditional mixin handles this.

vars: are templated late, after the host's variable scope is built — otherwise you couldn't reference inventory vars from a task.

Block

A Block is a YAML element that groups tasks:

- block:
    - command: risky-thing
    - command: other-risky-thing
  rescue:
    - debug: msg=Something failed; cleaning up
  always:
    - command: cleanup
  when: enable_risky | default(false)
  become: true
  tags:
    - risky

Implementation in lib/ansible/playbook/block.py. A block has three task lists:

  • block: — the main tasks. Run unconditionally (subject to when).
  • rescue: — runs if any block task fails. Clears the failure for the host.
  • always: — runs after block/rescue, regardless of failure.

Block-level keywords (become, tags, when, etc.) propagate to every task inside the block via attribute inheritance — the play iterator merges them when materializing each task.

Blocks can nest. The data model and the iterator handle arbitrary nesting depth, though most playbooks stop at 1-2 levels.

Block as universal grouping

Internally, every task list in a play is wrapped in a Block. play.tasks isn't a list[Task]; it's a list[Block]. A bare task at YAML level becomes an implicit single-task block. This unifies the iterator's view: it always walks blocks.

Block._get_parent_attribute() is what makes "play-level become: true propagates to every task in the play" work — a task's effective become is the first non-default value walking up parent → block → play.

TaskInclude

include_tasks (and the legacy include) are dynamic includes — resolved at task execution time. TaskInclude extends Task because it has the same modifiers (when, tags, vars). At iteration time, the strategy plugin sees a TaskInclude, asks IncludedFile to load the file, expands the result into a list of Blocks, and splices them into the per-host iterator state.

import_tasks is different: it's static, resolved at parse time by lib/ansible/playbook/helpers.py:load_list_of_blocks. The included file is parsed and its blocks are inserted into the parent play's task list before execution begins. when: on an import_tasks propagates to every imported task; on include_tasks the include itself is conditional but the imported tasks aren't pre-conditioned.

Loop and loop_control

loop: is the modern syntax for iterating a task. The value is a list (or a templating expression that resolves to a list); the task runs once per item.

- name: ensure users
  user:
    name: '{{ item.name }}'
    state: present
  loop:
    - { name: alice }
    - { name: bob }
  loop_control:
    label: '{{ item.name }}'
    pause: 1

loop_control (lib/ansible/playbook/loop_control.py:LoopControl) is a small object with:

  • loop_var — name of the variable holding each item (default item).
  • index_var — name of the variable holding the index.
  • label — what to display in callback output (lets you avoid leaking sensitive item content).
  • pause — sleep N seconds between iterations.
  • extended — set extra ansible_loop_* vars per iteration.

The legacy with_<lookup> keywords still work — they're translated by ModuleArgsParser into a loop plus the appropriate lookup.

Handler

A handler is a task that runs only when notified. lib/ansible/playbook/handler.py:Handler is a Task subclass with a listen: field added. Handlers live in a separate task list (play.handlers) and are triggered by notify: on regular tasks.

tasks:
  - name: install nginx
    package:
      name: nginx
    notify:
      - reload nginx

handlers:
  - name: reload nginx
    service:
      name: nginx
      state: reloaded

Handler ordering is definition order, not notification order. If you notify: [a, b] and the handler list is [b, a], b runs first.

listen: lets multiple handlers respond to a single name:

handlers:
  - name: reload nginx
    listen: web-config-changed
    service: name=nginx state=reloaded
  - name: reload haproxy
    listen: web-config-changed
    service: name=haproxy state=reloaded

A notify: web-config-changed triggers both. Implementation in lib/ansible/playbook/handler.py and lib/ansible/plugins/strategy/__init__.py.

Conditional

The Conditional mixin (lib/ansible/playbook/conditional.py) provides evaluate_conditional(templar, all_vars). It walks the when list (each entry is a Jinja expression) and ANDs them all together. Returns a bool.

failed_when and changed_when use the same mechanism but evaluate against the task result.

Integration points

  • Imported by: lib/ansible/playbook/play.py, the executor, the strategy plugins.
  • Imports: lib/ansible/parsing/mod_args.py (action parser), lib/ansible/_internal/_templating/_engine.py (conditionals), lib/ansible/plugins/loader.py (action resolution).

Entry points for modification

  • A new task keyword — declare a FieldAttribute on Task. Update lib/ansible/keyword_desc.yml.
  • A new block-level keyword — same on Block. Make sure inheritance through _get_parent_attribute works.
  • A new include semantics — be ready for a deep dive. The static/dynamic distinction was hard-fought; new include keywords are unlikely to be accepted.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Task and block – Ansible wiki | Factory