Krikri::Task
Represents a single task in a playbook
Constants
Ansible-core ships import_playbook as a doc-only stub module
(lib/ansible/modules/import_playbook.py: DOCUMENTATION and EXAMPLES
only, no code, no shebang). It is a play-level keyword only; a TASK
whose action is one of these spellings still RESOLVES as a module on
the controller (the loader finds the stub file), and the base action
plugin's _execute_module then dies on its shebang check - a per-task
failure "Task failed: module (X) is missing interpreter line" that
honors ignore_errors:, NOT the graceful unported-module skip the
rest of the unavailable_module flow gives (live-verified vs 2.19.11,
issue #762).
Constructors
Instance methods
The module spelling AS WRITTEN in the task source (before FQCN
resolution and before MODULE_ALIASES redirect) - Ansible's
"Unsupported parameters for (...) module" argument-validation error
echoes this spelling verbatim for non-delegating modules (bare
lineinfile: prints "(lineinfile)", the FQCN prints the FQCN), so
argspec validation needs it alongside the resolved module_name.
nil for tasks built outside the parser (defaults to module_name).
The module spelling AS WRITTEN in the task source (before FQCN
resolution and before MODULE_ALIASES redirect) - Ansible's
"Unsupported parameters for (...) module" argument-validation error
echoes this spelling verbatim for non-delegating modules (bare
lineinfile: prints "(lineinfile)", the FQCN prints the FQCN), so
argspec validation needs it alongside the resolved module_name.
nil for tasks built outside the parser (defaults to module_name).
The namespace.collection this task's own role belongs to -
exposed as the ansible_collection_name magic var, only ever set
for a role invoked via its full namespace.collection.role FQCN
(a collection-shipped role, as opposed to a plain Galaxy role
install). Several real collections use it to strip their own
namespace prefix back off ansible_parent_role_names (prometheus.
prometheus._common's own regex_replace(ansible_collection_name ~ '.', ''), computing a short service/tag name from the invoking
role's FQCN).
The namespace.collection this task's own role belongs to -
exposed as the ansible_collection_name magic var, only ever set
for a role invoked via its full namespace.collection.role FQCN
(a collection-shipped role, as opposed to a plain Galaxy role
install). Several real collections use it to strip their own
namespace prefix back off ansible_parent_role_names (prometheus.
prometheus._common's own regex_replace(ansible_collection_name ~ '.', ''), computing a short service/tag name from the invoking
role's FQCN).
The parser only sees the task's raw YAML, so a templated value ("{{ n }}") cannot be turned into seconds here - ansible resolves these per host at execution time. Keeping the raw text lets the executor do that resolution instead of silently dropping async behavior for every templated value.
The parser only sees the task's raw YAML, so a templated value ("{{ n }}") cannot be turned into seconds here - ansible resolves these per host at execution time. Keeping the raw text lets the executor do that resolution instead of silently dropping async behavior for every templated value.
async: / poll: - run the module in the background (as a detached OS process, not a Fiber, so it outlives the poll loop) up to async: seconds, checking every poll: seconds (default 10, matching Ansible) until it finishes or the async: timeout elapses; poll: 0 returns immediately with the job id instead of waiting at all. Local connections only - see TaskExecutor#execute_async.
async: / poll: - run the module in the background (as a detached OS process, not a Fiber, so it outlives the poll loop) up to async: seconds, checking every poll: seconds (default 10, matching Ansible) until it finishes or the async: timeout elapses; poll: 0 returns immediately with the job id instead of waiting at all. Local connections only - see TaskExecutor#execute_async.
Raw {{ ... }} text when become: is a templated expression rather
than a literal boolean (ansible-community.ansible-vault's own
become: "{{ vault_privileged_install }}", defaulting false).
become above still holds a best-effort parse-time guess (used as
a fallback if this can't be rendered for some reason), but the
executor re-renders and overrides it at execution time, once real
host/role vars are available - parse time never has that context.
Raw {{ ... }} text when become: is a templated expression rather
than a literal boolean (ansible-community.ansible-vault's own
become: "{{ vault_privileged_install }}", defaulting false).
become above still holds a best-effort parse-time guess (used as
a fallback if this can't be rendered for some reason), but the
executor re-renders and overrides it at execution time, once real
host/role vars are available - parse time never has that context.
Raw become_flags: keyword text (Ansible appends it to the sudo
become plugin's fixed argument set); templating happens at execution
time, like become_user's.
Raw become_flags: keyword text (Ansible appends it to the sudo
become plugin's fixed argument set); templating happens at execution
time, like become_user's.
The clauses a block: contributes to this task through Ansible's
PARENT CHAIN, kept apart from the merged when_condition because
that chain also reaches the tasks a dynamic include loads: real
evaluates a block's when: once per executed child, and an
include_tasks:/include_role: child's own file re-evaluates it
too (live-verified against 2.21.6 with jetune.docker_swarm_master,
issue #990: the block's fact read is reported after the FIRST
included task's banner, Origin'd at the block's own clause item).
The include statement's OWN when: stays out of this chain - real
evaluates that once, at the include, and never for its children.
origins maps each clause to its own YAML token so the clauses keep
the block's Origin instead of the inner task's.
The clauses a block: contributes to this task through Ansible's
PARENT CHAIN, kept apart from the merged when_condition because
that chain also reaches the tasks a dynamic include loads: real
evaluates a block's when: once per executed child, and an
include_tasks:/include_role: child's own file re-evaluates it
too (live-verified against 2.21.6 with jetune.docker_swarm_master,
issue #990: the block's fact read is reported after the FIRST
included task's banner, Origin'd at the block's own clause item).
The include statement's OWN when: stays out of this chain - real
evaluates that once, at the include, and never for its children.
origins maps each clause to its own YAML token so the clauses keep
the block's Origin instead of the inner task's.
Raw (unrendered) name: of every enclosing block:/rescue:/always:
wrapper, outermost first - nil for a task with no enclosing block.
ansible-core 2.19 templates a BLOCK's name keyword strictly
when one of its children actually goes to run, failing that child
with "Task failed: Error processing keyword 'name': 'X' is
undefined" (ikke_t.podman_container_systemd round 813203: the block
named do tasks when "{{ service_name }}" state is "running"
hard-failed Ansible when the role ran without grafana_podman's
container_name above it - this engine rendered the name leniently
and kept executing). A TASK's own name stays lenient there (Ansible banners it as "<< error 1 - 'nope' is undefined >>" and
still runs/skips it normally), so only the enclosing-chain names
are tracked.
Raw (unrendered) name: of every enclosing block:/rescue:/always:
wrapper, outermost first - nil for a task with no enclosing block.
ansible-core 2.19 templates a BLOCK's name keyword strictly
when one of its children actually goes to run, failing that child
with "Task failed: Error processing keyword 'name': 'X' is
undefined" (ikke_t.podman_container_systemd round 813203: the block
named do tasks when "{{ service_name }}" state is "running"
hard-failed Ansible when the role ran without grafana_podman's
container_name above it - this engine rendered the name leniently
and kept executing). A TASK's own name stays lenient there (Ansible banners it as "<< error 1 - 'nope' is undefined >>" and
still runs/skips it normally), so only the enclosing-chain names
are tracked.
Parallel to block_name_chain: each entry is the origin context of the
corresponding enclosing block's own name: VALUE (first character of
the rendered-to-be scalar), nil when no source map located it - the
child task's own file section has no name: key for the block, so
the keyword-failure chain's middle link must be pointed at the
block's own line the way ansible-core 2.19 does.
Parallel to block_name_chain: each entry is the origin context of the
corresponding enclosing block's own name: VALUE (first character of
the rendered-to-be scalar), nil when no source map located it - the
child task's own file section has no name: key for the block, so
the keyword-failure chain's middle link must be pointed at the
block's own line the way ansible-core 2.19 does.
block: / rescue: / always: - only set when module_name == "_block" (a pseudo-module marking this Task as a block rather than a plugin invocation). Blocks can nest, since these are themselves Task lists.
block: / rescue: / always: - only set when module_name == "_block" (a pseudo-module marking this Task as a block rather than a plugin invocation). Blocks can nest, since these are themselves Task lists.
changed_when: / failed_when: - override the module's own changed/failed verdict with a condition evaluated against the task's result (accessible via its own register: name, same as any other post-task condition; a bare literal like "false" needs no register: at all). Same string/eval pipeline as when_condition/until_condition: substituted for {{ }} expressions, then handed to ConditionalEvaluator.
changed_when: / failed_when: - override the module's own changed/failed verdict with a condition evaluated against the task's result (accessible via its own register: name, same as any other post-task condition; a bare literal like "false" needs no register: at all). Same string/eval pipeline as when_condition/until_condition: substituted for {{ }} expressions, then handed to ConditionalEvaluator.
Raw {{ ... }} text when check_mode: is a templated expression
rather than a literal boolean - same deferred-evaluation shape
become_expr uses, and evaluated in TaskExecutor#
resolve_task_check_mode against live vars.
Raw {{ ... }} text when check_mode: is a templated expression
rather than a literal boolean - same deferred-evaluation shape
become_expr uses, and evaluated in TaskExecutor#
resolve_task_check_mode against live vars.
Raw text of a task-level check_mode:/diff: literal that ansible's
boolean() post-validation refuses (issue #735: diff: banana,
diff: 2). Real fails the TASK with "Error processing keyword
'diff': The value 'banana' could not be converted to 'bool'." at
keyword-validation time; krikri's parse previously read both as nil
("unset") and silently ran the task. Recorded here instead of
raising at parse time so play/block/include-apply scopes (where the
parse result only seeds an inheritable default) keep their lenient
behavior, and the executor can fail the task with the same
keyword-failure shape become:'s non-bool error uses.
Raw text of a task-level check_mode:/diff: literal that ansible's
boolean() post-validation refuses (issue #735: diff: banana,
diff: 2). Real fails the TASK with "Error processing keyword
'diff': The value 'banana' could not be converted to 'bool'." at
keyword-validation time; krikri's parse previously read both as nil
("unset") and silently ran the task. Recorded here instead of
raising at parse time so play/block/include-apply scopes (where the
parse result only seeds an inheritable default) keep their lenient
behavior, and the executor can fail the task with the same
keyword-failure shape become:'s non-bool error uses.
connection: - overrides the connection plugin for just this task (almost always "local"), independent of delegate_to: - it changes HOW the task's module runs (locally on the controller vs. over SSH), not WHICH host's vars/facts/register apply (that's still delegate_to:'s job). robertdebock.backup's own "Create backup_ directory" (writes to the controller's filesystem while still being attributed to the current host's own inventory_hostname) uses exactly this combination: connection: local, no delegate_to:.
connection: - overrides the connection plugin for just this task (almost always "local"), independent of delegate_to: - it changes HOW the task's module runs (locally on the controller vs. over SSH), not WHICH host's vars/facts/register apply (that's still delegate_to:'s job). robertdebock.backup's own "Create backup_ directory" (writes to the controller's filesystem while still being attributed to the current host's own inventory_hostname) uses exactly this combination: connection: local, no delegate_to:.
delegate_facts: - when true alongside delegate_to:, a module's returned ansible_facts (set_fact:, fact-gathering modules) attach to the delegate_to: target's own hostvars instead of the delegating host's - Ansible's own documented meaning ("apply facts to a delegated host instead of the inventory_hostname"). register: is unaffected either way (always attaches to the delegating host).
delegate_facts: - when true alongside delegate_to:, a module's returned ansible_facts (set_fact:, fact-gathering modules) attach to the delegate_to: target's own hostvars instead of the delegating host's - Ansible's own documented meaning ("apply facts to a delegated host instead of the inventory_hostname"). register: is unaffected either way (always attaches to the delegating host).
delegate_to: - run this task's actual module/connection against a different host than the one the play is iterating (e.g. "localhost"), while variables/facts/register/stats still belong to the original host. May be templated ({{ vars }}), so kept as a raw string and resolved at execution time (same reasoning as include_file).
delegate_to: - run this task's actual module/connection against a different host than the one the play is iterating (e.g. "localhost"), while variables/facts/register/stats still belong to the original host. May be templated ({{ vars }}), so kept as a raw string and resolved at execution time (same reasoning as include_file).
Raw {{ ... }} text when diff: is a templated expression rather
than a literal boolean - same deferred-evaluation shape
check_mode_expr uses, resolved against live vars at execution time
(TaskExecutor#resolve_task_diff_mode). martin-micimo.haproxy's
diff: "{{ haproxy_show_template_diffs }}" used to leave
diff_mode nil, so the plugin got the CLI --diff fallback (off in
the role-tester runs) and its template tasks printed no diff
block where ansible prints one.
Raw {{ ... }} text when diff: is a templated expression rather
than a literal boolean - same deferred-evaluation shape
check_mode_expr uses, resolved against live vars at execution time
(TaskExecutor#resolve_task_diff_mode). martin-micimo.haproxy's
diff: "{{ haproxy_show_template_diffs }}" used to leave
diff_mode nil, so the plugin got the CLI --diff fallback (off in
the role-tester runs) and its template tasks printed no diff
block where ansible prints one.
The merged module_defaults in force for this task's scope, stamped
by the apply_module_defaults post-pass (play defaults + every
enclosing block/task scope). Ansible's module_defaults context is a
RUNTIME stack, so it also covers tasks an include_tasks: loads at
run time - which no parse-time pass ever sees. The include sites
hand this to apply_module_defaults for the tasks they parse then
(agaffney.synology_dsm's wrapper block gives its included login.yml
the synology_dsm_api_request base_url, without which the plugin
falls back to localhost and the failure message diverges, issue
#399).
The merged module_defaults in force for this task's scope, stamped
by the apply_module_defaults post-pass (play defaults + every
enclosing block/task scope). Ansible's module_defaults context is a
RUNTIME stack, so it also covers tasks an include_tasks: loads at
run time - which no parse-time pass ever sees. The include sites
hand this to apply_module_defaults for the tasks they parse then
(agaffney.synology_dsm's wrapper block gives its included login.yml
the synology_dsm_api_request base_url, without which the plugin
falls back to localhost and the failure message diverges, issue
#399).
environment: - per-task env vars (Ansible keyword). Raw,
unsubstituted string values, same convention as params - the
executor substitutes them at run time and forwards the result to
the plugin, which applies them around its own shelled-out commands.
environment: - per-task env vars (Ansible keyword). Raw,
unsubstituted string values, same convention as params - the
executor substitutes them at run time and forwards the result to
the plugin, which applies them around its own shelled-out commands.
Raw {{ ... }} string form of environment: (environment: "{{ proxy_env }}", ryandaniels.server_update_reboot's own apt/yum
tasks). Ansible accepts a single templated value here and
evaluates it to the env-var dict at task finalization - failing the
task when the referenced variable is undefined ("Error processing
keyword 'environment': 'proxy_env' is undefined"). The parser has
no vars context to resolve it against, so the raw text is stashed
here and the executor substitutes it strictly at run time.
Raw {{ ... }} string form of environment: (environment: "{{ proxy_env }}", ryandaniels.server_update_reboot's own apt/yum
tasks). Ansible accepts a single templated value here and
evaluates it to the env-var dict at task finalization - failing the
task when the referenced variable is undefined ("Error processing
keyword 'environment': 'proxy_env' is undefined"). The parser has
no vars context to resolve it against, so the raw text is stashed
here and the executor substitutes it strictly at run time.
Whether the task had its own name: key in the YAML. Real's
--list-tasks prints task.action (no role prefix) when task.name
is unset and task.get_name() ("role : name") otherwise; krikri
names unnamed tasks with their as-written action, so without this
flag the two are indistinguishable and an unnamed task inside a
role wrongly gained the "role : " prefix in the listing.
Whether the task had its own name: key in the YAML. Real's
--list-tasks prints task.action (no role prefix) when task.name
is unset and task.get_name() ("role : name") otherwise; krikri
names unnamed tasks with their as-written action, so without this
flag the two are indistinguishable and an unnamed task inside a
role wrongly gained the "role : " prefix in the listing.
Whether the task WROTE its own ignore_errors: (vs inheriting the
default) - block: children inherit the block's value only when they
define none of their own, and without this marker an explicit
ignore_errors: false on a child is indistinguishable from the
default false, so the block's value would clobber it either way.
Whether the task WROTE its own ignore_errors: (vs inheriting the
default) - block: children inherit the block's value only when they
define none of their own, and without this marker an explicit
ignore_errors: false on a child is indistinguishable from the
default false, so the block's value would clobber it either way.
Raw {{ ... }} text when ignore_errors: is a templated expression
rather than a literal boolean (dj-wasabi.telegraf's own
ignore_errors: "{{ ansible_check_mode }}", whose parse-time
fallback guess below is wrong on Ansible runs - see
TaskExecutor#resolve_task_ignore_errors, which re-renders this
against live vars (ansible_check_mode is bound there) and
overrides the guess, the same deferred-evaluation shape
check_mode_expr/become_expr use).
Raw {{ ... }} text when ignore_errors: is a templated expression
rather than a literal boolean (dj-wasabi.telegraf's own
ignore_errors: "{{ ansible_check_mode }}", whose parse-time
fallback guess below is wrong on Ansible runs - see
TaskExecutor#resolve_task_ignore_errors, which re-renders this
against live vars (ansible_check_mode is bound there) and
overrides the guess, the same deferred-evaluation shape
check_mode_expr/become_expr use).
ignore_unreachable: true - an unreachable host does not fail the
play at this task; it is reported, counted as ignored, and the host
carries on to the next task (which may itself be unreachable).
ignore_unreachable: true - an unreachable host does not fail the
play at this task; it is reported, counted as ignored, and the host
carries on to the next task (which may itself be unreachable).
The import_tasks: line's own when: clauses WITH each clause's exact
source position (path, 1-based line/col of the clause's YAML token),
recorded on EVERY task the import statically inlines - unlike
inherited_when_origin above, also when the child carries its own
when:. Ansible evaluates a merged import when: clause-by-clause with
the IMPORT's items first (they prefix the merged list), so a failing
import clause's conditional-failure chain "caused by" Origin belongs
at that clause's position in the IMPORTING file - even when the
child's own file word-for-word contains the same clause and would
otherwise be mislabeled (daixijun.mysql issue #328, where both
levels write the identical inventory_hostname in groups[...]). If
the import line's when: is a list, each item maps to its own item
line; a scalar maps to the when: value token (the shape
inherited_when_origin already covers for childless tasks). Keys are
the exact clause strings placed into Task#when_condition_list, so
the executor can look up the failing clause directly. nil when no
import when: contributed clauses with a locatable position.
The import_tasks: line's own when: clauses WITH each clause's exact
source position (path, 1-based line/col of the clause's YAML token),
recorded on EVERY task the import statically inlines - unlike
inherited_when_origin above, also when the child carries its own
when:. Ansible evaluates a merged import when: clause-by-clause with
the IMPORT's items first (they prefix the merged list), so a failing
import clause's conditional-failure chain "caused by" Origin belongs
at that clause's position in the IMPORTING file - even when the
child's own file word-for-word contains the same clause and would
otherwise be mislabeled (daixijun.mysql issue #328, where both
levels write the identical inventory_hostname in groups[...]). If
the import line's when: is a list, each item maps to its own item
line; a scalar maps to the when: value token (the shape
inherited_when_origin already covers for childless tasks). Keys are
the exact clause strings placed into Task#when_condition_list, so
the executor can look up the failing clause directly. nil when no
import when: contributed clauses with a locatable position.
apply: on an include_tasks:/include_role: statement - Ansible builds an
implicit parent BLOCK from that mapping (task_include.py's
build_parent_block), so every task the include loads inherits these as
block-level defaults, with each task's own keyword still winning. The
mapping is shape-validated at load time; these fields hold the parsed
values, which the executor's include paths merge into the tasks once
the included file (or role) has actually been loaded - the include's
own become/check_mode resolve into inherited there, its vars/when
are applied per loaded task. tags: IS carried (include_apply_tags):
tag selection runs over the play's own task list once (TagFilter)
AND again over every run-time-loaded include's tasks, and the
apply: tags join the inherited context of that second pass.
A templated become:/check_mode: inside apply: is
taken at its parse-time value, the same approximation parse_block_task
makes for a block's own templated become:.
Found via pandemonium1986.ohmyzsh: include_tasks: {apply: {become: true, become_user: "{{ loop_ohmyzsh_users.user_name }}"}} validated
cleanly and was then dropped on the floor, so the included tasks ran
with the play's own become_user (root) - the oh-my-zsh installer wrote
/root/.zshrc instead of the user's, and lineinfile failed with
"Destination /home/pandemonium//.zshrc does not exist !" on a host
where ansible-playbook succeeded.
apply: on an include_tasks:/include_role: statement - Ansible builds an
implicit parent BLOCK from that mapping (task_include.py's
build_parent_block), so every task the include loads inherits these as
block-level defaults, with each task's own keyword still winning. The
mapping is shape-validated at load time; these fields hold the parsed
values, which the executor's include paths merge into the tasks once
the included file (or role) has actually been loaded - the include's
own become/check_mode resolve into inherited there, its vars/when
are applied per loaded task. tags: IS carried (include_apply_tags):
tag selection runs over the play's own task list once (TagFilter)
AND again over every run-time-loaded include's tasks, and the
apply: tags join the inherited context of that second pass.
A templated become:/check_mode: inside apply: is
taken at its parse-time value, the same approximation parse_block_task
makes for a block's own templated become:.
Found via pandemonium1986.ohmyzsh: include_tasks: {apply: {become: true, become_user: "{{ loop_ohmyzsh_users.user_name }}"}} validated
cleanly and was then dropped on the floor, so the included tasks ran
with the play's own become_user (root) - the oh-my-zsh installer wrote
/root/.zshrc instead of the user's, and lineinfile failed with
"Destination /home/pandemonium//.zshrc does not exist !" on a host
where ansible-playbook succeeded.
apply: {tags: [...]} on an include_tasks:/include_role: - unlike
the statement's own tags:, these ARE given to every task the include
loads (Ansible's implicit apply-Block carries them), so the
executor's runtime tag filter merges them into the inherited
context (live-verified vs 2.19.11: apply tags change
--tags/--skip-tags/never selection of the loaded tasks exactly like
an enclosing block's tags would). nil when no apply: was given.
apply: {tags: [...]} on an include_tasks:/include_role: - unlike
the statement's own tags:, these ARE given to every task the include
loads (Ansible's implicit apply-Block carries them), so the
executor's runtime tag filter merges them into the inherited
context (live-verified vs 2.19.11: apply tags change
--tags/--skip-tags/never selection of the loaded tasks exactly like
an enclosing block's tags would). nil when no apply: was given.
include_tasks: - only set when module_name == "_include_tasks". Unlike import_tasks (resolved at parse time), the file path may be templated ({{ vars }}) and isn't resolved until this task actually runs, so both the raw path and the directory to resolve it against (wherever the include_tasks: line itself lives) are carried on the Task for the executor to use at run time.
include_tasks: - only set when module_name == "_include_tasks". Unlike import_tasks (resolved at parse time), the file path may be templated ({{ vars }}) and isn't resolved until this task actually runs, so both the raw path and the directory to resolve it against (wherever the include_tasks: line itself lives) are carried on the Task for the executor to use at run time.
The same value as include_file when the playbook wrote it as a
non-string YAML literal (file: 21), kept native: Ansible's
fatal include dump echoes the value as written - "include": 21,
not "include": "21" - while the path it is resolved from is the
Python str() of it. nil for every ordinary (string) path.
The same value as include_file when the playbook wrote it as a
non-string YAML literal (file: 21), kept native: Ansible's
fatal include dump echoes the value as written - "include": 21,
not "include": "21" - while the path it is resolved from is the
Python str() of it. nil for every ordinary (string) path.
The include_role: directive's own args AS WRITTEN (name/tasks_from/
vars_from/apply/allow_duplicates/...), unrendered. Ansible's IncludeRole
result carries them back out under include_args - but RENDERED, per
invocation (live-verified vs 2.19.11: name: "{{ rn }}" registers
include_args.name as the resolved role name) - so the executor renders
each value when it builds the registered result (issue #888: the
register: on an include_role:/include_tasks: statement used to leave
the variable undefined for every later when: that referenced it).
The include_role: directive's own args AS WRITTEN (name/tasks_from/
vars_from/apply/allow_duplicates/...), unrendered. Ansible's IncludeRole
result carries them back out under include_args - but RENDERED, per
invocation (live-verified vs 2.19.11: name: "{{ rn }}" registers
include_args.name as the resolved role name) - so the executor renders
each value when it builds the registered result (issue #888: the
register: on an include_role:/include_tasks: statement used to leave
the variable undefined for every later when: that referenced it).
An include_role:'s own boolean keyword (public/allow_duplicates/ rolespec_validate) whose value real cannot convert to a Python bool
- Ansible reports that before it resolves the role at all, as a normal task failure (see RoleBoolKeywords). nil for every convertible (or templated) value, and always nil for a static import_role:, where the same failure is a playbook-load abort instead.
An include_role:'s own boolean keyword (public/allow_duplicates/ rolespec_validate) whose value real cannot convert to a Python bool
- Ansible reports that before it resolves the role at all, as a normal task failure (see RoleBoolKeywords). nil for every convertible (or templated) value, and always nil for a static import_role:, where the same failure is a playbook-load abort instead.
The enclosing play's collections: at parse time - the runtime
include_role search-path display carries that prefix (live-verified
vs 2.19.11), but the executor only has the task, not the play.
The enclosing play's collections: at parse time - the runtime
include_role search-path display carries that prefix (live-verified
vs 2.19.11), but the executor only has the task, not the play.
For an include_role: whose loop: is a literal YAML sequence, each
entry's own line/column. Real's looped role-not-found Origin points
at the failing ITEM's position, not the name: value (live-verified
vs 2.19.11). nil when the loop is not a literal sequence.
For an include_role: whose loop: is a literal YAML sequence, each
entry's own line/column. Real's looped role-not-found Origin points
at the failing ITEM's position, not the name: value (live-verified
vs 2.19.11). nil when the loop is not a literal sequence.
include_role: - only set when module_name == "_include_role". The dynamic counterpart to a roles: list entry: resolved at execution time (role name may be templated), via RoleLoader, same as roles:.
include_role: - only set when module_name == "_include_role". The dynamic counterpart to a roles: list entry: resolved at execution time (role name may be templated), via RoleLoader, same as roles:.
The name: (or role:) value's own line/column inside the task's
source - the runtime role-not-found failure's Origin block points
there, not at the task's first key (live-verified vs 2.19.11).
The name: (or role:) value's own line/column inside the task's
source - the runtime role-not-found failure's Origin block points
there, not at the task's first key (live-verified vs 2.19.11).
A DYNAMIC include_role: whose name/role: value is present but NOT a string - real's IncludeRole.load only requires the KEY to exist, the type check happens later in RoleInclude.load ("Invalid role definition."), which for the dynamic form runs at RUN time, after the task banner has printed (issue #761). Holds the raw value (as JSON) so the executor can abort the run the way real's escaped exception does; a static import_role:'s same value refuses the whole playbook at parse time instead (see parse_include_role) and never reaches the executor.
A DYNAMIC include_role: whose name/role: value is present but NOT a string - real's IncludeRole.load only requires the KEY to exist, the type check happens later in RoleInclude.load ("Invalid role definition."), which for the dynamic form runs at RUN time, after the task banner has printed (issue #761). Holds the raw value (as JSON) so the executor can abort the run the way real's escaped exception does; a static import_role:'s same value refuses the whole playbook at parse time instead (see parse_include_role) and never reaches the executor.
tasks_from: - loads tasks/<name>.yml instead of tasks/main.yml. Common in collection-shipped "shared logic" roles (e.g. prometheus.prometheus's own _common role, invoked repeatedly by every exporter role with a different tasks_from: per call - one role directory, several distinct task-file entry points).
tasks_from: - loads tasks/<name>.yml instead of tasks/main.yml. Common in collection-shipped "shared logic" roles (e.g. prometheus.prometheus's own _common role, invoked repeatedly by every exporter role with a different tasks_from: per call - one role directory, several distinct task-file entry points).
vars_from: - loads vars/<name>.yml instead of vars/main.yml as the
role's own vars. Issue #128 found krikri parsed (and validated) the
key but discarded it, so a role gated on a var defined only in the
alt vars file failed its when: as undefined while Ansible passed.
vars_from: - loads vars/<name>.yml instead of vars/main.yml as the
role's own vars. Issue #128 found krikri parsed (and validated) the
key but discarded it, so a role gated on a var defined only in the
alt vars file failed its when: as undefined while Ansible passed.
vars: on an include_tasks: statement - visible to every task in the included file (unlike import_tasks:'s vars:, which is merged directly into each imported task at parse time, this has to be carried on the Task and propagated at execution time, same as loop:'s item).
vars: on an include_tasks: statement - visible to every task in the included file (unlike import_tasks:'s vars:, which is merged directly into each imported task at parse time, this has to be carried on the Task and propagated at execution time, same as loop:'s item).
dir:-mode parameters (Ansible's include_vars directory form,
the Ansible module) - include_vars_dir is
the (possibly templated) directory to load every vars file from;
include_vars_depth is the raw depth: string (templated, resolved
at run time - Ansible's depth: 0 default means UNLIMITED
recursion, depth: 1 means top-level files only); files_matching/
ignore_files/extensions/ignore_unknown_extensions mirror the
module's own options. The executor's execute_include_vars_dir
owns the details.
dir:-mode parameters (Ansible's include_vars directory form,
the Ansible module) - include_vars_dir is
the (possibly templated) directory to load every vars file from;
include_vars_depth is the raw depth: string (templated, resolved
at run time - Ansible's depth: 0 default means UNLIMITED
recursion, depth: 1 means top-level files only); files_matching/
ignore_files/extensions/ignore_unknown_extensions mirror the
module's own options. The executor's execute_include_vars_dir
owns the details.
2.20 made a non-list extensions: an error ("The 'extensions'
option must be a list."); a bare string used to be wrapped into a
one-element list silently.
2.20 made a non-list extensions: an error ("The 'extensions'
option must be a list."); a bare string used to be wrapped into a
one-element list silently.
include_vars: - only set when module_name == "_include_vars".
include_vars_file is the file to load (may be templated, so it is
resolved at run time); include_vars_name is the optional name:
parameter, which loads the file into a single dict variable of that
name instead of merging its keys into the context.
include_vars: - only set when module_name == "_include_vars".
include_vars_file is the file to load (may be templated, so it is
resolved at run time); include_vars_name is the optional name:
parameter, which loads the file into a single dict variable of that
name instead of merging its keys into the context.
The Python type name of a TRUTHY non-string file:/dir: value.
Ansible's include_vars action plugin touches that value itself there -
os.path.join(current_dir, self.source_dir) for dir, and the
dataloader's source.split('/') (2.21's path_dwim_relative_stack,
which used to reach source.startswith('~') instead) for file - and
crashes with the type name in the message, before any str() copy of
the path is taken (live-verified vs 2.21.6). A FALSY non-string value
(0, 0.0, false, "", [], {}, null) is not an error at all: Ansible's
if not self.source_dir and not self.source_file check discards it
and the lookup goes on with a None needle, which is exactly the
plain null-file failure this engine already produces. nil for every
ordinary (string) value.
The Python type name of a TRUTHY non-string file:/dir: value.
Ansible's include_vars action plugin touches that value itself there -
os.path.join(current_dir, self.source_dir) for dir, and the
dataloader's source.split('/') (2.21's path_dwim_relative_stack,
which used to reach source.startswith('~') instead) for file - and
crashes with the type name in the message, before any str() copy of
the path is taken (live-verified vs 2.21.6). A FALSY non-string value
(0, 0.0, false, "", [], {}, null) is not an error at all: Ansible's
if not self.source_dir and not self.source_file check discards it
and the lookup goes on with a None needle, which is exactly the
plain null-file failure this engine already produces. nil for every
ordinary (string) value.
2.20 deprecated a bare-string ignore_files: (still accepted, one
deprecation warning per run) - removed in 2.24.
2.20 deprecated a bare-string ignore_files: (still accepted, one
deprecation warning per run) - removed in 2.24.
The first include_vars: argument Ansible's action-level validation loop rejects ("<key> is not a valid option in include_vars"), and whether a file:-style key appears beside a dir:-style key (the "You are mixing file only and dir only arguments" failure). Both fire at RUN time in Ansible, never at parse - see execute_include_vars.
The first include_vars: argument Ansible's action-level validation loop rejects ("<key> is not a valid option in include_vars"), and whether a file:-style key appears beside a dir:-style key (the "You are mixing file only and dir only arguments" failure). Both fire at RUN time in Ansible, never at parse - see execute_include_vars.
The task-argument key ansible-core pins its Origin on for a top-level
fact read inside include_vars's own path: the free-form statement's
include_vars: key, file: in the mapping form, dir: for dir mode
(live-verified against 2.21.6 - each names that value's own column).
The path is templated by the executor rather than the generic task-arg
finalization, so the key has to be kept from parse time.
The task-argument key ansible-core pins its Origin on for a top-level
fact read inside include_vars's own path: the free-form statement's
include_vars: key, file: in the mapping form, dir: for dir mode
(live-verified against 2.21.6 - each names that value's own column).
The path is templated by the executor rather than the generic task-arg
finalization, so the key has to be kept from parse time.
loop_control.index_var - exposes the current loop iteration's
zero-based index under this variable name (Ansible's own
loop_control: { index_var: idx }, commonly paired with a
register:ed loop result so some_registered.results[idx] can be
looked up against the SAME item currently being processed - e.g.
results[index].stat.exists as a when: guard skipping re-work
already verified by an earlier per-item stat: loop). Previously
entirely unimplemented - parsed nowhere, injected into vars_context
nowhere - so {{ index }} (or whatever name was configured) always
resolved to "undefined" throughout the loop body, silently breaking
any downstream results[index] lookup. Found via robertdebock.
mount's own "Create mountpoint" task.
loop_control.index_var - exposes the current loop iteration's
zero-based index under this variable name (Ansible's own
loop_control: { index_var: idx }, commonly paired with a
register:ed loop result so some_registered.results[idx] can be
looked up against the SAME item currently being processed - e.g.
results[index].stat.exists as a when: guard skipping re-work
already verified by an earlier per-item stat: loop). Previously
entirely unimplemented - parsed nowhere, injected into vars_context
nowhere - so {{ index }} (or whatever name was configured) always
resolved to "undefined" throughout the loop body, silently breaking
any downstream results[index] lookup. Found via robertdebock.
mount's own "Create mountpoint" task.
Environment layers inherited from the play and any enclosing
blocks, outermost first. Ansible's environment: is an
extend=True keyword: the play's env, then each enclosing block's,
then the task's own merge into one dict with the innermost value
winning per variable - so a play-level environment: {PATH: ...}
applies to every task in the play even when the task sets nothing
of its own. Snapshotted from Play#environment_layers at parse
time (the ambient carrier parse_block_task raises/lowers around
its children); a layer is either an already-parsed dict
(templates still raw inside) or the raw templated string form,
substituted and merged in order at execution.
Environment layers inherited from the play and any enclosing
blocks, outermost first. Ansible's environment: is an
extend=True keyword: the play's env, then each enclosing block's,
then the task's own merge into one dict with the innermost value
winning per variable - so a play-level environment: {PATH: ...}
applies to every task in the play even when the task sets nothing
of its own. Snapshotted from Play#environment_layers at parse
time (the ambient carrier parse_block_task raises/lowers around
its children); a layer is either an already-parsed dict
(templates still raw inside) or the raw templated string form,
substituted and merged in order at execution.
Context tags this task carries from OUTSIDE itself - the play's own
tags, every enclosing block's tags, a roles:/import_ * invocation's
tags - but never the task's own declared tags:. Written by
TagFilter at every selection point (play task list, block children,
and each run-time-loaded include), it is what an
include_tasks:/include_role: passes down to the tasks it loads at
run time: those inherit these plus apply: tags, while the include
statement's OWN tags stay with the statement (Ansible gives dynamic
includes' children the parent-chain tags only - live-verified vs
2.19.11: a tags:-bearing include_tasks: does not tag its
children, but an enclosing block's, a play's, a roles: entry's and
an import_role:'s tags all do).
Context tags this task carries from OUTSIDE itself - the play's own
tags, every enclosing block's tags, a roles:/import_ * invocation's
tags - but never the task's own declared tags:. Written by
TagFilter at every selection point (play task list, block children,
and each run-time-loaded include), it is what an
include_tasks:/include_role: passes down to the tasks it loads at
run time: those inherit these plus apply: tags, while the include
statement's OWN tags stay with the statement (Ansible gives dynamic
includes' children the parent-chain tags only - live-verified vs
2.19.11: a tags:-bearing include_tasks: does not tag its
children, but an enclosing block's, a play's, a roles: entry's and
an import_role:'s tags all do).
The file/line/col of an import_tasks: line's own when: VALUE token
that was statically ANDed onto this task - the position real 2.19
points the conditional-failure chain's "caused by" Origin at for an
inlined task (the IMPORTING file, not the inlined task's own file,
where no when: exists to locate; Jooho.okd_prerequisites issue
#252). nil when the task carries no statically inherited import
when:, or when its own when: also contributed to the merged
condition.
The file/line/col of an import_tasks: line's own when: VALUE token
that was statically ANDed onto this task - the position real 2.19
points the conditional-failure chain's "caused by" Origin at for an
inlined task (the IMPORTING file, not the inlined task's own file,
where no when: exists to locate; Jooho.okd_prerequisites issue
#252). nil when the task carries no statically inherited import
when:, or when its own when: also contributed to the merged
condition.
True when this _include_role task actually came from import_role:
(statically resolved), not include_role: (dynamic). Both currently
share the same runtime inclusion machinery (see parse_include_role's
own comment on that pragmatic approximation), but Ansible's
import_role: produces NO task result of its own at all - no "TASK
[...]" banner, no ok/skipped recap increment - since it's a true
parse-time splice; only include_role: (genuinely dynamic) does.
Used by the executor to suppress the wrapper's own display/counting
for the import_role: case while still running the included role's
own tasks (each gets its own normal banner) exactly the same way
either directive reaches them. Found via round171's robertdebock.
revealmd (import_role: name: robertdebock.service): Ansible's
recap was ok=17, crystal's was ok=18 - an extra "Create revealmd
service" TASK banner + ok that Ansible never shows at all.
True when this _include_role task actually came from import_role:
(statically resolved), not include_role: (dynamic). Both currently
share the same runtime inclusion machinery (see parse_include_role's
own comment on that pragmatic approximation), but Ansible's
import_role: produces NO task result of its own at all - no "TASK
[...]" banner, no ok/skipped recap increment - since it's a true
parse-time splice; only include_role: (genuinely dynamic) does.
Used by the executor to suppress the wrapper's own display/counting
for the import_role: case while still running the included role's
own tasks (each gets its own normal banner) exactly the same way
either directive reaches them. Found via round171's robertdebock.
revealmd (import_role: name: robertdebock.service): Ansible's
recap was ok=17, crystal's was ok=18 - an extra "Create revealmd
service" TASK banner + ok that Ansible never shows at all.
Ansible's handler listen: accepts a single topic string OR a
list of topics (CVi.thanos's own handlers/main.yml listens on three
at once, round 811339) - same single-string-or-list shape as
notify: above, parsed identically. nil when absent.
Ansible's handler listen: accepts a single topic string OR a
list of topics (CVi.thanos's own handlers/main.yml listens on three
at once, round 811339) - same single-string-or-list shape as
notify: above, parsed identically. nil when absent.
loop_control.extended - exposes the ansible_loop dict (index,
first/last, allitems, nextitem/previtem, ...) for the iteration.
loop_control.extended - exposes the ansible_loop dict (index,
first/last, allitems, nextitem/previtem, ...) for the iteration.
with_file: entries - unlike with_fileglob (a pattern to match
filenames), each entry names a specific file whose CONTENT becomes
item (Ansible's file lookup plugin). Resolved at execution
time for the same reasons as loop_fileglob (needs {{ vars }}
substitution and filesystem access, plus role_path for a relative
entry - conventionally searched under the role's own files/ dir).
with_file: entries - unlike with_fileglob (a pattern to match
filenames), each entry names a specific file whose CONTENT becomes
item (Ansible's file lookup plugin). Resolved at execution
time for the same reasons as loop_fileglob (needs {{ vars }}
substitution and filesystem access, plus role_path for a relative
entry - conventionally searched under the role's own files/ dir).
with_fileglob patterns, resolved at execution time (needs {{ vars }} substitution and filesystem access, neither available at parse time).
with_fileglob patterns, resolved at execution time (needs {{ vars }} substitution and filesystem access, neither available at parse time).
with_community.general.filetree sources - the directories to walk,
kept as their raw ({{ }}-unsubstituted) strings since a source is
ordinarily {{ role_path }}/templates/<something> and can only be
resolved at execution time once the variable context exists. See
FiletreeLookup for the real lookup-plugin semantics the executor's
resolve_loop_filetree hands these to.
with_community.general.filetree sources - the directories to walk,
kept as their raw ({{ }}-unsubstituted) strings since a source is
ordinarily {{ role_path }}/templates/<something> and can only be
resolved at execution time once the variable context exists. See
FiletreeLookup for the real lookup-plugin semantics the executor's
resolve_loop_filetree hands these to.
with_first_found candidate paths, resolved at execution time for the
same reasons as loop_fileglob. Unlike a glob this yields at most one
item - the first candidate that exists. loop_first_found_skip is the
skip: true form, which makes "none of them exist" a skipped task
rather than an error.
with_first_found candidate paths, resolved at execution time for the
same reasons as loop_fileglob. Unlike a glob this yields at most one
item - the first candidate that exists. loop_first_found_skip is the
skip: true form, which makes "none of them exist" a skipped task
rather than an error.
with_first_found:'s own paths: sub-key (as opposed to the lookup('first_found', {files:, paths:})/query() function-call idiom, which already threads paths: through via evaluate_first_found since it goes through a raw hash, not this dedicated keyword parser). Previously silently discarded - resolution always fell back to the hardcoded files/templates/vars/role-root search roots regardless of what paths: actually specified.
with_first_found:'s own paths: sub-key (as opposed to the lookup('first_found', {files:, paths:})/query() function-call idiom, which already threads paths: through via evaluate_first_found since it goes through a raw hash, not this dedicated keyword parser). Previously silently discarded - resolution always fell back to the hardcoded files/templates/vars/role-root search roots regardless of what paths: actually specified.
true only for the scalar string form (with_first_found: "{{ var }}"
- parse_first_found wraps it into a one-element list, so the executor
couldn't otherwise tell it apart from a one-element literal list).
The distinction decides WHERE the undefined-ness of a reference is
surfaced: Ansible templates the keyword's own value strictly
(a scalar
{{ undefined_var }}source fails the task), but hands a literal list's candidate strings to the first_found lookup plugin, which templates each term leniently - an undefined reference inside a list candidate renders to nothing and just never matches a file.
true only for the scalar string form (with_first_found: "{{ var }}"
- parse_first_found wraps it into a one-element list, so the executor
couldn't otherwise tell it apart from a one-element literal list).
The distinction decides WHERE the undefined-ness of a reference is
surfaced: Ansible templates the keyword's own value strictly
(a scalar
{{ undefined_var }}source fails the task), but hands a literal list's candidate strings to the first_found lookup plugin, which templates each term leniently - an undefined reference inside a list candidate renders to nothing and just never matches a file.
with_community.general.flattened sources, kept as their raw task
strings. Each is ordinarily a {{ some_list_var }} reference to a
list; like loop_fileglob/loop_first_found they can only be resolved at
execution time once the variable context exists, so the parser stores
them verbatim and TaskExecutor flattens the resolved lists.
with_community.general.flattened sources, kept as their raw task
strings. Each is ordinarily a {{ some_list_var }} reference to a
list; like loop_fileglob/loop_first_found they can only be resolved at
execution time once the variable context exists, so the parser stores
them verbatim and TaskExecutor flattens the resolved lists.
Loop items already resolved at parse time (loop:, with_items:, with_dict:, with_nested:, with_sequence:, with_indexed_items:).
Loop items already resolved at parse time (loop:, with_items:, with_dict:, with_nested:, with_sequence:, with_indexed_items:).
True only when loop_items came from a literal with_items: array
(not loop:, which has no such behavior). Ansible's with_items:
implicitly applies flatten(levels=1) across ALL rendered elements -
with_items: ["{{ list_a }}", "{{ list_b }}"] where each renders to
its own list yields one iteration per INNER element (list_a's items
then list_b's items), not one iteration per outer element holding a
whole list as item. Found via nicolai86.prepare-release's own
with_items: ["{{ default_directories }}", "{{ directories }}"].
True only when loop_items came from a literal with_items: array
(not loop:, which has no such behavior). Ansible's with_items:
implicitly applies flatten(levels=1) across ALL rendered elements -
with_items: ["{{ list_a }}", "{{ list_b }}"] where each renders to
its own list yields one iteration per INNER element (list_a's items
then list_b's items), not one iteration per outer element holding a
whole list as item. Found via nicolai86.prepare-release's own
with_items: ["{{ default_directories }}", "{{ directories }}"].
loop_control.label - what the per-item result line shows instead of
the raw item, e.g. label: "{{ item.name }}" to keep a big dict
out of the output.
loop_control.label - what the per-item result line shows instead of
the raw item, e.g. label: "{{ item.name }}" to keep a big dict
out of the output.
Which keyword carried a LITERAL loop list (loop: vs with_items:).
loop_template_kind only marks loop sources that need templating, but
the executor's undefined-item Origin reporting must still land under
the keyword that actually holds the failing list element - ansible
reports with_items: there, not its generic loop: default
(live-verified vs 2.19.11, issue #228).
Which keyword carried a LITERAL loop list (loop: vs with_items:).
loop_template_kind only marks loop sources that need templating, but
the executor's undefined-item Origin reporting must still land under
the keyword that actually holds the failing list element - ansible
reports with_items: there, not its generic loop: default
(live-verified vs 2.19.11, issue #228).
Any OTHER legacy with_<lookup>: source (with_url:, with_lines:,
with_env:, with_pipe:, ...) - Ansible treats ANY with_-prefixed
task key as a loop keyword equivalent to
loop: "{{ lookup('<plugin>', <terms>, wantlist=True) }}", with the
terms templated first. Kept raw (plugin name + term list) and
resolved at execution time by TaskExecutor#resolve_loop_lookup;
without this the keyword silently fell through as an unrecognized
task key and the task ran exactly ONCE with the loop variable
unbound ("'sha_url_item' is undefined" - lean_delivery.
solr_standalone's sha512 fetch).
Any OTHER legacy with_<lookup>: source (with_url:, with_lines:,
with_env:, with_pipe:, ...) - Ansible treats ANY with_-prefixed
task key as a loop keyword equivalent to
loop: "{{ lookup('<plugin>', <terms>, wantlist=True) }}", with the
terms templated first. Kept raw (plugin name + term list) and
resolved at execution time by TaskExecutor#resolve_loop_lookup;
without this the keyword silently fell through as an unrecognized
task key and the task ran exactly ONCE with the loop variable
unbound ("'sha_url_item' is undefined" - lean_delivery.
solr_standalone's sha512 fetch).
with_nested: given as an array whose entries include one or more
{{ ... }}-templated scalars (the classic
with_nested: ["{{ users }}", "{{ groups }}"] shape). The cartesian
product's FACTOR SIZES are only knowable at execution time (a source
var's real length, including zero), so unlike a fully-literal
with_nested array this can't be resolved by LoopResolver at parse
time - the old parse-time branch wrapped every templated scalar as a
ONE-element literal list, pinning each factor to size 1 and iterating
once with item = the whole rendered list, no matter how many
elements the variable actually held. Sources are kept as their raw
strings (a literal sub-array entry serializes to JSON text) and
resolved + multiplied by TaskExecutor#resolve_loop_nested, mirroring
loop_flattened's own defer-until-runtime design.
with_nested: given as an array whose entries include one or more
{{ ... }}-templated scalars (the classic
with_nested: ["{{ users }}", "{{ groups }}"] shape). The cartesian
product's FACTOR SIZES are only knowable at execution time (a source
var's real length, including zero), so unlike a fully-literal
with_nested array this can't be resolved by LoopResolver at parse
time - the old parse-time branch wrapped every templated scalar as a
ONE-element literal list, pinning each factor to size 1 and iterating
once with item = the whole rendered list, no matter how many
elements the variable actually held. Sources are kept as their raw
strings (a literal sub-array entry serializes to JSON text) and
resolved + multiplied by TaskExecutor#resolve_loop_nested, mirroring
loop_flattened's own defer-until-runtime design.
with_subelements: the raw list template (usually a {{ registered_var .results }} reference) and the subelement key. Both kept verbatim and
resolved at execution time once the variable context + registered vars
exist.
with_subelements: the raw list template (usually a {{ registered_var .results }} reference) and the subelement key. Both kept verbatim and
resolved at execution time once the variable context + registered vars
exist.
true only for the single-element-array template form (with_items: ["{{ some_list }}"]) - see #find_loop_template's own comment for why
that shape deliberately flattens a resolved-to-scalar value into one
loop item instead of raising. The direct scalar form (loop: "{{ var }}", false here) does NOT get that legacy flattening: Ansible
hard-fails a loop:/with_items: source that resolves to anything
other than a real list ("The loop value must resolve to a 'list',
not '<type>'.") - round174 differential matrix scenarios 11a/11c,
live-verified against ansible-core 2.19.12. Only the array-wrapped
form keeps the old lenient single-item behavior
(loop_scalar_flatten_spec.cr).
true only for the single-element-array template form (with_items: ["{{ some_list }}"]) - see #find_loop_template's own comment for why
that shape deliberately flattens a resolved-to-scalar value into one
loop item instead of raising. The direct scalar form (loop: "{{ var }}", false here) does NOT get that legacy flattening: Ansible
hard-fails a loop:/with_items: source that resolves to anything
other than a real list ("The loop value must resolve to a 'list',
not '<type>'.") - round174 differential matrix scenarios 11a/11c,
live-verified against ansible-core 2.19.12. Only the array-wrapped
form keeps the old lenient single-item behavior
(loop_scalar_flatten_spec.cr).
loop:/with_items:/with_dict:/with_nested:/with_indexed_items: given as a Jinja variable reference ("{{ some_var }}") rather than a literal inline list/dict. Unresolvable at parse time since the YAML value is just a scalar string until the variable context exists, so the raw source keyword and template string are carried for the executor to resolve at execution time (mirrors loop_fileglob).
loop:/with_items:/with_dict:/with_nested:/with_indexed_items: given as a Jinja variable reference ("{{ some_var }}") rather than a literal inline list/dict. Unresolvable at parse time since the YAML value is just a scalar string until the variable context exists, so the raw source keyword and template string are carried for the executor to resolve at execution time (mirrors loop_fileglob).
with_together: given as an array whose entries include {{ ... }}
-templated scalars (the classic with_together: ["{{ users }}", "{{ groups }}"] shape, manala.accounts's own manala_accounts_users
pairing). Same defer-until-runtime design as loop_nested_sources
right above: the zip's row count is only knowable once the variable
context exists (a source var's real length, including zero - the
role defaults both to empty lists), so the raw source strings are
kept here and resolved + zipped by TaskExecutor#resolve_loop_together.
with_together: given as an array whose entries include {{ ... }}
-templated scalars (the classic with_together: ["{{ users }}", "{{ groups }}"] shape, manala.accounts's own manala_accounts_users
pairing). Same defer-until-runtime design as loop_nested_sources
right above: the zip's row count is only knowable once the variable
context exists (a source var's real length, including zero - the
role defaults both to empty lists), so the raw source strings are
kept here and resolved + zipped by TaskExecutor#resolve_loop_together.
loop_control.loop_var - the variable name the loop item is exposed
under (Ansible default "item"). Roles like dev-sec os_hardening set
loop_control: { loop_var: mount } so an include_tasks/loop can refer
to mount.path, mount.owner, etc. rather than always item. Kept
verbatim and resolved at execution time; nil means the default "item".
loop_control.loop_var - the variable name the loop item is exposed
under (Ansible default "item"). Roles like dev-sec os_hardening set
loop_control: { loop_var: mount } so an include_tasks/loop can refer
to mount.path, mount.owner, etc. rather than always item. Kept
verbatim and resolved at execution time; nil means the default "item".
meta: - only set when module_name == "_meta". Holds the meta action ("clear_facts"); see TaskExecutor#execute_meta.
meta: - only set when module_name == "_meta". Holds the meta action ("clear_facts"); see TaskExecutor#execute_meta.
Whether the task's module argument was written as a bare STRING
(raw: echo hi) rather than a mapping (raw: {cmd: echo hi}): the
two end up with identical params (the string lands under cmd for
the command-style modules), but Ansible routes them differently -
a bare string always becomes the free-form module's _raw_params,
while a mapping only carries the keys it names. The executor's raw:
gate (see TaskExecutor#raw_missing_raw_params_failure) needs the
distinction: real's raw action reads ONLY _raw_params, so a
mapping-form raw without one crashes where the bare string runs.
Whether the task's module argument was written as a bare STRING
(raw: echo hi) rather than a mapping (raw: {cmd: echo hi}): the
two end up with identical params (the string lands under cmd for
the command-style modules), but Ansible routes them differently -
a bare string always becomes the free-form module's _raw_params,
while a mapping only carries the keys it names. The executor's raw:
gate (see TaskExecutor#raw_missing_raw_params_failure) needs the
distinction: real's raw action reads ONLY _raw_params, so a
mapping-form raw without one crashes where the bare string runs.
Block- and task-scope module_defaults: - see Play#module_defaults.
no_log: true - suppress this task's result detail. A SECURITY
control: it is how a playbook keeps a password, token or key out
of the log. Previously unparsed and unused, so every such task
printed its secret in full.
no_log: true - suppress this task's result detail. A SECURITY
control: it is how a playbook keeps a password, token or key out
of the log. Previously unparsed and unused, so every such task
printed its secret in full.
True when the task wrote its own no_log: (nearest scope wins over a
block's no_log: that its children inherit).
True when the task wrote its own no_log: (nearest scope wins over a
block's no_log: that its children inherit).
Raw {{ ... }} text when no_log: is a templated expression rather
than a literal boolean (newrelic.newrelic-infra's own no_log: "{{ nrinfragent_hide_config_values }}", defaulting false). The
parse-time guess above (parse_become_value) defaults ANY templated
value to true - the safe direction for a SECURITY control (never
under-hides a real secret), but it means a task like this one has
its failure message suppressed on EVERY run regardless of the
real value, masking Ansible errors for debugging (found chasing
newrelic.newrelic-infra's own merge_yaml failure, whose actual
message was invisible in every log because of this). Re-rendered
in TaskExecutor#resolve_task_no_log against live vars, same
deferred-evaluation shape ignore_errors_expr/check_mode_expr use.
Raw {{ ... }} text when no_log: is a templated expression rather
than a literal boolean (newrelic.newrelic-infra's own no_log: "{{ nrinfragent_hide_config_values }}", defaulting false). The
parse-time guess above (parse_become_value) defaults ANY templated
value to true - the safe direction for a SECURITY control (never
under-hides a real secret), but it means a task like this one has
its failure message suppressed on EVERY run regardless of the
real value, masking Ansible errors for debugging (found chasing
newrelic.newrelic-infra's own merge_yaml failure, whose actual
message was invisible in every log because of this). Re-rendered
in TaskExecutor#resolve_task_no_log against live vars, same
deferred-evaluation shape ignore_errors_expr/check_mode_expr use.
ansible emits a task-level reserved-name warning from VariableManager.get_vars - once the strategy actually reaches the task, not while the playbook loads (issue #473: a load-time print fires even for a task an earlier failure keeps from ever running). The parse-time scan parks the text here; the executor prints it just before the task's banner, or never when the play ends first.
ansible emits a task-level reserved-name warning from VariableManager.get_vars - once the strategy actually reaches the task, not while the playbook loads (issue #473: a load-time print fires even for a task an earlier failure keeps from ever running). The parse-time scan parks the text here; the executor prints it just before the task's banner, or never when the play ends first.
The module name resolve_templated_action rendered OUT of templated_action, stamped on the shallow copy it returns - the shared original task must keep the as-written template text, but display/register decisions that key on the module (debug's changed-stripping) run later against that original and need the resolved spelling; they read it back off the task's result.
The module name resolve_templated_action rendered OUT of templated_action, stamped on the shallow copy it returns - the shared original task must keep the as-written template text, but display/register decisions that key on the module (debug's changed-stripping) run later against that original and need the resolved spelling; they read it back off the task's result.
retries:/delay: given as Jinja templates (collivier.xtesting does retries: "{{ ((jenkins_wait | int) / 5) | int }}") cannot become a number at parse time: real ansible templates both per task against the task's own vars, so the raw string is kept for run_until_retries to resolve and the numeric properties above only hold the implicit defaults (3/5) for that case.
retries:/delay: given as Jinja templates (collivier.xtesting does retries: "{{ ((jenkins_wait | int) / 5) | int }}") cannot become a number at parse time: real ansible templates both per task against the task's own vars, so the raw string is kept for run_until_retries to resolve and the numeric properties above only hold the implicit defaults (3/5) for that case.
Run-time dedup identity of the meta/main.yml DEPENDENCY invocation
whose load produced this task (issue #320): krikri used to collapse
a duplicate dependency at LOAD time, but real ansible's decision is
per-host and run-time - an identical re-invocation is silently
skipped only when an EARLIER identical invocation actually completed
a non-skipped task for that host, so a shared dependency whose every
task was skipped ({ role: c, when: ansible_distribution == 'MacOSX' }
on a Linux host) still prints its full skipping: block on every later
reach. role_dedup_key is the shared identity string (RoleLoader's own
dedup key), role_occurrence_id a fresh token per load_role invocation,
so the executor's per-host tracker can tell "the owner occurrence"
(keeps running) from "a later identical occurrence" (gets silently
skipped). Nil for play-level roles:/import_role:/include_role: task
streams - those keep their own load-level dedup-per-call behaviour;
only meta/main.yml dependency loads participate (and
allow_duplicates: true loads are never tagged at all, so they keep
running every time).
Run-time dedup identity of the meta/main.yml DEPENDENCY invocation
whose load produced this task (issue #320): krikri used to collapse
a duplicate dependency at LOAD time, but real ansible's decision is
per-host and run-time - an identical re-invocation is silently
skipped only when an EARLIER identical invocation actually completed
a non-skipped task for that host, so a shared dependency whose every
task was skipped ({ role: c, when: ansible_distribution == 'MacOSX' }
on a Linux host) still prints its full skipping: block on every later
reach. role_dedup_key is the shared identity string (RoleLoader's own
dedup key), role_occurrence_id a fresh token per load_role invocation,
so the executor's per-host tracker can tell "the owner occurrence"
(keeps running) from "a later identical occurrence" (gets silently
skipped). Nil for play-level roles:/import_role:/include_role: task
streams - those keep their own load-level dedup-per-call behaviour;
only meta/main.yml dependency loads participate (and
allow_duplicates: true loads are never tagged at all, so they keep
running every time).
Where each of the role layers' raw values was defined - the Origin real prints when a task NAME's template chain fails inside one of them (see VarOrigin). Mirrors role_defaults/role_vars' merges, so a lookup here answers for exactly the value vars_context serves.
Where each of the role layers' raw values was defined - the Origin real prints when a task NAME's template chain fails inside one of them (see VarOrigin). Mirrors role_defaults/role_vars' merges, so a lookup here answers for exactly the value vars_context serves.
Set on every task loaded from a role (tasks/main.yml and handlers/main.yml alike) by RoleLoader. role_defaults is the lowest precedence tier (role's defaults/main.yml); role_vars sits above play/host vars but below the task's own vars: (role's vars/main.yml, merged with the role invocation's own vars:). role_files_dir/ role_templates_dir let the executor resolve a copy:/template: src: relative to the role's files//templates/ directory, since the plugin subprocess itself has no concept of roles.
Set on every task loaded from a role (tasks/main.yml and handlers/main.yml alike) by RoleLoader. role_defaults is the lowest precedence tier (role's defaults/main.yml); role_vars sits above play/host vars but below the task's own vars: (role's vars/main.yml, merged with the role invocation's own vars:). role_files_dir/ role_templates_dir let the executor resolve a copy:/template: src: relative to the role's files//templates/ directory, since the plugin subprocess itself has no concept of roles.
Identity of ONE dynamic include_role: execution (a fresh random
token per run_include_role_once - per host, per loop item). What
meta: end_role keys its per-host "role ended" flag on: two
invocations of the same role in one play are independent for
end_role purposes (verified against ansible-core 2.19.4 - a looped
include_role whose first item ends the role still runs the second
item in full). Statically loaded role tasks (roles:/import_role:)
leave this nil and key on role_path instead - a static role body
executes exactly once per play, so the path is already a unique
invocation identity there.
Identity of ONE dynamic include_role: execution (a fresh random
token per run_include_role_once - per host, per loop item). What
meta: end_role keys its per-host "role ended" flag on: two
invocations of the same role in one play are independent for
end_role purposes (verified against ansible-core 2.19.4 - a looped
include_role whose first item ends the role still runs the second
item in full). Statically loaded role tasks (roles:/import_role:)
leave this nil and key on role_path instead - a static role body
executes exactly once per play, so the path is already a unique
invocation identity there.
The role's invocation name, exactly as written in role:/name:
(a bare name or a full local path) - exposed to templates as the
ansible_role_name magic var (dev-sec nginx_hardening's own
hardening.conf.j2: # Generated by Ansible role {{ ansible_role_ name }}). Ansible sets this to the same string the play
actually invoked the role with, not a normalized basename -
verified against a ansible-playbook run using a full local
path for role:, which echoed that exact path back.
The role's invocation name, exactly as written in role:/name:
(a bare name or a full local path) - exposed to templates as the
ansible_role_name magic var (dev-sec nginx_hardening's own
hardening.conf.j2: # Generated by Ansible role {{ ansible_role_ name }}). Ansible sets this to the same string the play
actually invoked the role with, not a normalized basename -
verified against a ansible-playbook run using a full local
path for role:, which echoed that exact path back.
The chain of ancestor role names (root-first, NOT including this
role's own name) that led to this role being invoked via
include_role: from within another role's own tasks - exposed as
the ansible_parent_role_names magic var. Empty/nil for a role
listed directly under a play's own roles: (not nested inside any
other role). Several real collections (prometheus.prometheus's own
_common shared-logic role, invoked by every exporter role) guard
against direct invocation with ansible_parent_role_names is defined and ansible_parent_role_names | length > 0.
The chain of ancestor role names (root-first, NOT including this
role's own name) that led to this role being invoked via
include_role: from within another role's own tasks - exposed as
the ansible_parent_role_names magic var. Empty/nil for a role
listed directly under a play's own roles: (not nested inside any
other role). Several real collections (prometheus.prometheus's own
_common shared-logic role, invoked by every exporter role) guard
against direct invocation with ansible_parent_role_names is defined and ansible_parent_role_names | length > 0.
The role_path (filesystem root) of each ancestor role, same
ordering/population rules as role_parent_names above -
Ansible searches a role task's ENTIRE parent-role chain (not just
the currently-executing role's own templates/files dir) for a
relative template:/copy: src:. Found via prometheus.prometheus._
common's own "Create systemd service unit" task: src: "{{ _common_service_name }}.service.j2" lives in the CALLING role's
own templates/ dir (node_exporter/templates/node_exporter.
service.j2), not _common's - a shared/generic role deliberately
relying on this Ansible search-path behavior to let each
exporter role supply its own service unit template.
The role_path (filesystem root) of each ancestor role, same
ordering/population rules as role_parent_names above -
Ansible searches a role task's ENTIRE parent-role chain (not just
the currently-executing role's own templates/files dir) for a
relative template:/copy: src:. Found via prometheus.prometheus._
common's own "Create systemd service unit" task: src: "{{ _common_service_name }}.service.j2" lives in the CALLING role's
own templates/ dir (node_exporter/templates/node_exporter.
service.j2), not _common's - a shared/generic role deliberately
relying on this Ansible search-path behavior to let each
exporter role supply its own service unit template.
The role's own root directory on disk - exposed to templates as the
role_path magic var (linux-system-roles/logging's own include_role: name: "{{ role_path }}/roles/rsyslog", a common pattern for a role to
reference one of its own private subroles by absolute path).
The role's own root directory on disk - exposed to templates as the
role_path magic var (linux-system-roles/logging's own include_role: name: "{{ role_path }}/roles/rsyslog", a common pattern for a role to
reference one of its own private subroles by absolute path).
The role's vars/ directory - where include_vars: and with_first_found: resolve a relative filename from, the same way role_files_dir serves copy:'s src:.
The role's vars/ directory - where include_vars: and with_first_found: resolve a relative filename from, the same way role_files_dir serves copy:'s src:.
run_once: - only actually execute this task for the first host in the play; later hosts skip it outright (no output/stats), same as Ansible.
run_once: - only actually execute this task for the first host in the play; later hosts skip it outright (no output/stats), same as Ansible.
Where this task's own YAML mapping starts in its source file -
Ansible's task-level Origin: position (the first key of the
task mapping, e.g. name at column 5 of - name: x). Sourced
from YamlSourceMap during parsing; line/col 0 = unknown (scan
failed or exotic structure), which suppresses origin-labeled error
output rather than mislabeling it.
Where this task's own YAML mapping starts in its source file -
Ansible's task-level Origin: position (the first key of the
task mapping, e.g. name at column 5 of - name: x). Sourced
from YamlSourceMap during parsing; line/col 0 = unknown (scan
failed or exotic structure), which suppresses origin-labeled error
output rather than mislabeling it.
True for tasks loaded from a HANDLER (and for everything such a
load nests further down): Ansible never tag-filters handlers or
anything a handler includes - a never-tagged handler runs when
notified under a plain invocation, and an inner never task in a
handler's include_tasks: file ran under --tags zzz (live-verified
vs 2.19.11). Set at the handler include path and propagated by the
executor's runtime filter so nested includes stay exempt.
True for tasks loaded from a HANDLER (and for everything such a
load nests further down): Ansible never tag-filters handlers or
anything a handler includes - a never-tagged handler runs when
notified under a plain invocation, and an inner never task in a
handler's include_tasks: file ran under --tags zzz (live-verified
vs 2.19.11). Set at the handler include path and propagated by the
executor's runtime filter so nested includes stay exempt.
A legacy action:/local_action: free-form directive whose module
name is itself a {{ }} template (action: "{{ ansible_pkg_mgr }} state=present name={{ item }}", jdauphant.intellij) can't resolve
at parse time - the raw free-form string is kept here and resolved
at execution (TaskExecutor#resolve_templated_action): the
substituted first token names the Ansible module, the rest re-parses
as its params. Deliberately distinct from unavailable_module: a
module name that is only resolvable at run time must FAIL the task
when it doesn't resolve (Ansible: "couldn't resolve
module/action 'x'"), never be skipped like an unknown module.
A legacy action:/local_action: free-form directive whose module
name is itself a {{ }} template (action: "{{ ansible_pkg_mgr }} state=present name={{ item }}", jdauphant.intellij) can't resolve
at parse time - the raw free-form string is kept here and resolved
at execution (TaskExecutor#resolve_templated_action): the
substituted first token names the Ansible module, the rest re-parses
as its params. Deliberately distinct from unavailable_module: a
module name that is only resolvable at run time must FAIL the task
when it doesn't resolve (Ansible: "couldn't resolve
module/action 'x'"), never be skipped like an unknown module.
Where templated_action's module NAME was written: the YAML key it
sits under (action/local_action as written, or module for the
mapping form, whose only templated token that is) plus that value
token's source position. ansible-core renders the name during task
post-validation and tags it with the value's own Origin, so a
top-level fact read while resolving it deprecates there
(live-verified against 2.21.6: the directive value's opening quote -
action: "{{ ansible_pkg_mgr }}" reports its column 11, the mapping
form's module: value its own). The position comes from the YAML
source map, not a text scan, because the directive can sit on the
task's own - action: ... line (galexrt.ntp, issue #988).
Where templated_action's module NAME was written: the YAML key it
sits under (action/local_action as written, or module for the
mapping form, whose only templated token that is) plus that value
token's source position. ansible-core renders the name during task
post-validation and tags it with the value's own Origin, so a
top-level fact read while resolving it deprecates there
(live-verified against 2.21.6: the directive value's opening quote -
action: "{{ ansible_pkg_mgr }}" reports its column 11, the mapping
form's module: value its own). The position comes from the YAML
source map, not a text scan, because the directive can sit on the
task's own - action: ... line (galexrt.ntp, issue #988).
throttle: - cap how many hosts run this task at once, below the
run's own --forks. 0/absent means no extra cap.
throttle: - cap how many hosts run this task at once, below the
run's own --forks. 0/absent means no extra cap.
timeout: - the per-task wall-clock limit in seconds (ansible-core's
TASK_TIMEOUT: the controller fails the task once it runs longer than
this). 0/absent means no limit. Templated values keep their raw
{{ }} text in timeout_expr and resolve per host at execution time,
the same deferred shape become_expr/check_mode_expr use.
timeout: - the per-task wall-clock limit in seconds (ansible-core's
TASK_TIMEOUT: the controller fails the task once it runs longer than
this). 0/absent means no limit. Templated values keep their raw
{{ }} text in timeout_expr and resolve per host at execution time,
the same deferred shape become_expr/check_mode_expr use.
Appends a short String representation of this object which includes its class name and its object address.
class Person
def initialize(@name : String, @age : Int32)
end
end
Person.new("John", 32).to_s # => #<Person:0x10a199f20>
A MAPPING-form legacy action:/local_action: whose module name is
DEFINITIVELY unresolvable on the controller (CollectionIndex#
controller_resolves? verdict resolves: false - no collection ships
it, nothing redirects to it). Ansible does not refuse such a
playbook at load the way it refuses a bare-key module name: the
mapping's module resolves lazily, so the task only fails (per host,
rc=2, "Cannot resolve '...' to an action or module.", issue #666)
when its own when: actually lets it run - behind when: false it
is an ordinary skip. Deliberately distinct from unavailable_module
(a name the controller CAN resolve but krikri has not ported -
those keep the graceful skip plus the exit-4 reachable_unavailable_
modules accounting); the executor's reached-unavailable paths key
on this flag to fail instead of skip.
A MAPPING-form legacy action:/local_action: whose module name is
DEFINITIVELY unresolvable on the controller (CollectionIndex#
controller_resolves? verdict resolves: false - no collection ships
it, nothing redirects to it). Ansible does not refuse such a
playbook at load the way it refuses a bare-key module name: the
mapping's module resolves lazily, so the task only fails (per host,
rc=2, "Cannot resolve '...' to an action or module.", issue #666)
when its own when: actually lets it run - behind when: false it
is an ordinary skip. Deliberately distinct from unavailable_module
(a name the controller CAN resolve but krikri has not ported -
those keep the graceful skip plus the exit-4 reachable_unavailable_
modules accounting); the executor's reached-unavailable paths key
on this flag to fail instead of skip.
The as-written module name the failure message echoes.
The mapping's own module: VALUE token - playbook path plus 1-based
(line, column) - which real 2.19.11 pins the chain's "caused by"
Origin at (6:17 in the issue #666 repro), distinct from the
task-level Origin at the task's name line. nil without a source map
(the stanza is then omitted, never mislabeled).
The mapping's own module: VALUE token - playbook path plus 1-based
(line, column) - which real 2.19.11 pins the chain's "caused by"
Origin at (6:17 in the issue #666 repro), distinct from the
task-level Origin at the task's name line. nil without a source map
(the stanza is then omitted, never mislabeled).
until: / retries: / delay: - retry a task until a condition passes.
True only on the role-synthesized validate_argument_spec task. Real
never templates that task's banner name: its name is built in
Python (Role._create_validation_task), not read from YAML, so under
2.19's template-trust model it is never trusted as a template -
a short_description containing {{ role_name }} (trallnag.endlessh,
issue #856) or even the always-defined {{ inventory_hostname }}
banners RAW, with no << error >> marker rewriting or warning. A
user-written validate_argument_spec task's name: comes from YAML
and templates normally, so this must only ride the synthesized one.
True only on the role-synthesized validate_argument_spec task. Real
never templates that task's banner name: its name is built in
Python (Role._create_validation_task), not read from YAML, so under
2.19's template-trust model it is never trusted as a template -
a short_description containing {{ role_name }} (trallnag.endlessh,
issue #856) or even the always-defined {{ inventory_hostname }}
banners RAW, with no << error >> marker rewriting or warning. A
user-written validate_argument_spec task's name: comes from YAML
and templates normally, so this must only ride the synthesized one.
True for a USER-written validate_argument_spec: task (any spelling),
false for the role-synthesized one. The inline form validates against
the task's vars context COMBINED with its own provided_arguments:
(ansible-core's action plugin always validates
combine_vars(args_from_task_vars, provided_arguments) - verified in
the 2.19.11 plugin source and live: a play var satisfies an inline
required option), and its validate_args_context is {} (the role
machinery is what injects the role context into the synthesized
task's args), while its [ERROR] block pins Origin at the task's own
source location instead of <unknown>.
True for a USER-written validate_argument_spec: task (any spelling),
false for the role-synthesized one. The inline form validates against
the task's vars context COMBINED with its own provided_arguments:
(ansible-core's action plugin always validates
combine_vars(args_from_task_vars, provided_arguments) - verified in
the 2.19.11 plugin source and live: a play var satisfies an inline
required option), and its validate_args_context is {} (the role
machinery is what injects the role context into the synthesized
task's args), while its [ERROR] block pins Origin at the task's own
source location instead of <unknown>.
Inline-only: the Python class name Ansible's datatag layer reports
for an argument_spec: that is neither a mapping nor a string.
Captured at parse time because the executor cannot recover it: a
literal dict is pre-parsed into validate_argument_spec_options and
both an explicit YAML null and an absent key read as nil there, and
the name follows the VALUE's type, not its content - real 2.19.11
fails the task with the value's own class (live-verified: bools ride
untagged <class 'bool'>, an explicit null is <class 'NoneType'>,
ints/lists the _AnsibleTagged* subclasses, floats the same rule)
BEFORE any option validation runs.
Inline-only: the Python class name Ansible's datatag layer reports
for an argument_spec: that is neither a mapping nor a string.
Captured at parse time because the executor cannot recover it: a
literal dict is pre-parsed into validate_argument_spec_options and
both an explicit YAML null and an absent key read as nil there, and
the name follows the VALUE's type, not its content - real 2.19.11
fails the task with the value's own class (live-verified: bools ride
untagged <class 'bool'>, an explicit null is <class 'NoneType'>,
ints/lists the _AnsibleTagged* subclasses, floats the same rule)
BEFORE any option validation runs.
Synthesized by RoleLoader when a role ships argument specs
(meta/argument_specs.yml/.yaml/.json, or the meta main file's
argument_specs: key) -
only set when module_name == "_validate_argument_spec". Ansible
auto-inserts this as the role's first task ("Validating arguments
against arg spec 'main' - <short_description>"); holds the entry
point's options: map (name -> {type, required, ...}) for
TaskExecutor#execute_validate_argument_spec to check the effective
vars against.
Synthesized by RoleLoader when a role ships argument specs
(meta/argument_specs.yml/.yaml/.json, or the meta main file's
argument_specs: key) -
only set when module_name == "_validate_argument_spec". Ansible
auto-inserts this as the role's first task ("Validating arguments
against arg spec 'main' - <short_description>"); holds the entry
point's options: map (name -> {type, required, ...}) for
TaskExecutor#execute_validate_argument_spec to check the effective
vars against.
Inline-only: the task's own provided_arguments: dict (literal
form). These override same-named vars in the vars context
(combine_vars order) and are templated at execution time.
Inline-only: the task's own provided_arguments: dict (literal
form). These override same-named vars in the vars context
(combine_vars order) and are templated at execution time.
Inline-only: an argument_spec: written as a Jinja STRING (the
real-world form
"{{ (lookup('ansible.builtin.file', role_path ~ '/meta/argument_specs.yml') | from_yaml).argument_specs.main.options }}")
is resolved at EXECUTION time with the task's vars context - a
literal dict is pre-parsed into validate_argument_spec_options
instead and this stays nil.
Inline-only: an argument_spec: written as a Jinja STRING (the
real-world form
"{{ (lookup('ansible.builtin.file', role_path ~ '/meta/argument_specs.yml') | from_yaml).argument_specs.main.options }}")
is resolved at EXECUTION time with the task's vars context - a
literal dict is pre-parsed into validate_argument_spec_options
instead and this stays nil.
The task's own vars: entries' defining positions (the task's own
source file) - same Origin purpose as the role layers above.
The task's own vars: entries' defining positions (the task's own
source file) - same Origin purpose as the role layers above.
The when: LIST's own per-item condition strings, kept alongside the
" and "-joined when_condition so the STRICT when: evaluation path
(TaskExecutor#evaluate_when) can type-check each item SEPARATELY,
exactly as ansible-core 2.19 does: a list-form when: is a sequence
of independent conditionals, each required to end in a real boolean
when: [str_var, bool_var]fails ("Conditional result ... was derived from value of type 'str'") even though the equivalent single-stringwhen: str_var and bool_varPASSES there (Python'sandreturns the last operand, so the whole expression's result type is bool and only the whole result gets the strict check - both behaviors verified live against 2.19.4). Joining the list into oneandstring and strict-checking only the JOINED result made crazikpl.logging'swhen: [(...or ...), use_rsyslog]pass silently where Ansible failed the task. nil when when: wasn't a multi-item list (single-item lists and scalars evaluate identically either way); the joinedwhen_conditionremains the source of truth for every non-strict consumer (loop pre-gates, register:/ batcher scans, block/import inheritance display).
The when: LIST's own per-item condition strings, kept alongside the
" and "-joined when_condition so the STRICT when: evaluation path
(TaskExecutor#evaluate_when) can type-check each item SEPARATELY,
exactly as ansible-core 2.19 does: a list-form when: is a sequence
of independent conditionals, each required to end in a real boolean
when: [str_var, bool_var]fails ("Conditional result ... was derived from value of type 'str'") even though the equivalent single-stringwhen: str_var and bool_varPASSES there (Python'sandreturns the last operand, so the whole expression's result type is bool and only the whole result gets the strict check - both behaviors verified live against 2.19.4). Joining the list into oneandstring and strict-checking only the JOINED result made crazikpl.logging'swhen: [(...or ...), use_rsyslog]pass silently where Ansible failed the task. nil when when: wasn't a multi-item list (single-item lists and scalars evaluate identically either way); the joinedwhen_conditionremains the source of truth for every non-strict consumer (loop pre-gates, register:/ batcher scans, block/import inheritance display).
--list-tasks/--list-tags display (TaskLister): the include-family
directive key AS WRITTEN (include_role:,
ansible.builtin.include_tasks:, ...). Real's listtasks CLI prints
the bare action for a task with no name: of its own - for the
include statements that is this key, not the synthesized
"<action> : <role>" display name Task#name carries for runtime
banners. nil for plain module tasks, whose AS-WRITTEN action is
already what Task#name holds when unnamed.
--list-tasks/--list-tags display (TaskLister): the include-family
directive key AS WRITTEN (include_role:,
ansible.builtin.include_tasks:, ...). Real's listtasks CLI prints
the bare action for a task with no name: of its own - for the
include statements that is this key, not the synthesized
"<action> : <role>" display name Task#name carries for runtime
banners. nil for plain module tasks, whose AS-WRITTEN action is
already what Task#name holds when unnamed.