mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Support 'apply' to apply attributes to included tasks - Impl 1 (#39236)
* Support 'apply' to apply attributes to included tasks * Cannot validate args for task_include * Only allow apply on include_ * Re-enable arg validation, but only for include_tasks and import_tasks * s/task/ir/ * Add tests for include_ apply * Include context with AnsibleParserError * Add docs for apply * version_added * Add free-form documentation back * Add example of free-form with apply
This commit is contained in:
parent
961484e00d
commit
da4ff18406
9 changed files with 175 additions and 4 deletions
|
@ -26,6 +26,10 @@ description:
|
||||||
- This module is also supported for Windows targets.
|
- This module is also supported for Windows targets.
|
||||||
version_added: "2.2"
|
version_added: "2.2"
|
||||||
options:
|
options:
|
||||||
|
apply:
|
||||||
|
description:
|
||||||
|
- Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to the tasks within the include.
|
||||||
|
version_added: '2.7'
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- The name of the role to be executed.
|
- The name of the role to be executed.
|
||||||
|
@ -89,6 +93,15 @@ EXAMPLES = """
|
||||||
include_role:
|
include_role:
|
||||||
name: myrole
|
name: myrole
|
||||||
when: not idontwanttorun
|
when: not idontwanttorun
|
||||||
|
|
||||||
|
- name: Apply tags to tasks within included file
|
||||||
|
include_role:
|
||||||
|
name: install
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
|
|
|
@ -23,10 +23,20 @@ description:
|
||||||
- Includes a file with a list of tasks to be executed in the current playbook.
|
- Includes a file with a list of tasks to be executed in the current playbook.
|
||||||
version_added: "2.4"
|
version_added: "2.4"
|
||||||
options:
|
options:
|
||||||
free-form:
|
file:
|
||||||
description:
|
description:
|
||||||
- The name of the imported file is specified directly without any other option.
|
- The name of the imported file is specified directly without any other option.
|
||||||
- Unlike M(import_tasks), most keywords, including loops and conditionals, apply to this statement.
|
- Unlike M(import_tasks), most keywords, including loops and conditionals, apply to this statement.
|
||||||
|
version_added: '2.7'
|
||||||
|
apply:
|
||||||
|
description:
|
||||||
|
- Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to the tasks within the include.
|
||||||
|
version_added: '2.7'
|
||||||
|
free-form:
|
||||||
|
description:
|
||||||
|
- |
|
||||||
|
Supplying a file name via free-form C(- include_tasks: file.yml) of a file to be included is the equivalent
|
||||||
|
of specifying an argument of I(file).
|
||||||
notes:
|
notes:
|
||||||
- This is a core feature of the Ansible, rather than a module, and cannot be overridden like a module.
|
- This is a core feature of the Ansible, rather than a module, and cannot be overridden like a module.
|
||||||
'''
|
'''
|
||||||
|
@ -51,6 +61,24 @@ EXAMPLES = """
|
||||||
- name: Include task list in play only if the condition is true
|
- name: Include task list in play only if the condition is true
|
||||||
include_tasks: "{{ hostvar }}.yaml"
|
include_tasks: "{{ hostvar }}.yaml"
|
||||||
when: hostvar is defined
|
when: hostvar is defined
|
||||||
|
|
||||||
|
- name: Apply tags to tasks within included file
|
||||||
|
include_tasks:
|
||||||
|
file: install.yml
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- name: Apply tags to tasks within included file when using free-form
|
||||||
|
include_tasks: install.yml
|
||||||
|
args:
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
|
|
|
@ -23,6 +23,7 @@ from os.path import basename
|
||||||
|
|
||||||
from ansible.errors import AnsibleParserError
|
from ansible.errors import AnsibleParserError
|
||||||
from ansible.playbook.attribute import FieldAttribute
|
from ansible.playbook.attribute import FieldAttribute
|
||||||
|
from ansible.playbook.block import Block
|
||||||
from ansible.playbook.task_include import TaskInclude
|
from ansible.playbook.task_include import TaskInclude
|
||||||
from ansible.playbook.role import Role
|
from ansible.playbook.role import Role
|
||||||
from ansible.playbook.role.include import RoleInclude
|
from ansible.playbook.role.include import RoleInclude
|
||||||
|
@ -45,7 +46,7 @@ class IncludeRole(TaskInclude):
|
||||||
|
|
||||||
BASE = ('name', 'role') # directly assigned
|
BASE = ('name', 'role') # directly assigned
|
||||||
FROM_ARGS = ('tasks_from', 'vars_from', 'defaults_from') # used to populate from dict in role
|
FROM_ARGS = ('tasks_from', 'vars_from', 'defaults_from') # used to populate from dict in role
|
||||||
OTHER_ARGS = ('private', 'allow_duplicates') # assigned to matching property
|
OTHER_ARGS = ('apply', 'private', 'allow_duplicates') # assigned to matching property
|
||||||
VALID_ARGS = tuple(frozenset(BASE + FROM_ARGS + OTHER_ARGS)) # all valid args
|
VALID_ARGS = tuple(frozenset(BASE + FROM_ARGS + OTHER_ARGS)) # all valid args
|
||||||
|
|
||||||
# =================================================================================
|
# =================================================================================
|
||||||
|
@ -134,6 +135,23 @@ class IncludeRole(TaskInclude):
|
||||||
from_key = key.replace('_from', '')
|
from_key = key.replace('_from', '')
|
||||||
ir._from_files[from_key] = basename(ir.args.get(key))
|
ir._from_files[from_key] = basename(ir.args.get(key))
|
||||||
|
|
||||||
|
apply_attrs = ir.args.pop('apply', {})
|
||||||
|
if apply_attrs and ir.action != 'include_role':
|
||||||
|
raise AnsibleParserError('Invalid options for %s: apply' % ir.action, obj=data)
|
||||||
|
elif apply_attrs:
|
||||||
|
apply_attrs['block'] = []
|
||||||
|
p_block = Block.load(
|
||||||
|
apply_attrs,
|
||||||
|
play=block._play,
|
||||||
|
parent_block=block,
|
||||||
|
role=role,
|
||||||
|
task_include=task_include,
|
||||||
|
use_handlers=block._use_handlers,
|
||||||
|
variable_manager=variable_manager,
|
||||||
|
loader=loader,
|
||||||
|
)
|
||||||
|
ir._parent = p_block
|
||||||
|
|
||||||
# manual list as otherwise the options would set other task parameters we don't want.
|
# manual list as otherwise the options would set other task parameters we don't want.
|
||||||
for option in my_arg_names.intersection(IncludeRole.OTHER_ARGS):
|
for option in my_arg_names.intersection(IncludeRole.OTHER_ARGS):
|
||||||
setattr(ir, option, ir.args.get(option))
|
setattr(ir, option, ir.args.get(option))
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleParserError
|
||||||
from ansible.playbook.attribute import FieldAttribute
|
from ansible.playbook.attribute import FieldAttribute
|
||||||
|
from ansible.playbook.block import Block
|
||||||
from ansible.playbook.task import Task
|
from ansible.playbook.task import Task
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -38,6 +40,10 @@ class TaskInclude(Task):
|
||||||
circumstances related to the `- include: ...` task.
|
circumstances related to the `- include: ...` task.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
BASE = frozenset(('file', '_raw_params')) # directly assigned
|
||||||
|
OTHER_ARGS = frozenset(('apply',)) # assigned to matching property
|
||||||
|
VALID_ARGS = BASE.union(OTHER_ARGS) # all valid args
|
||||||
|
|
||||||
# =================================================================================
|
# =================================================================================
|
||||||
# ATTRIBUTES
|
# ATTRIBUTES
|
||||||
|
|
||||||
|
@ -49,8 +55,38 @@ class TaskInclude(Task):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
|
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
|
||||||
t = TaskInclude(block=block, role=role, task_include=task_include)
|
ti = TaskInclude(block=block, role=role, task_include=task_include)
|
||||||
return t.load_data(data, variable_manager=variable_manager, loader=loader)
|
task = ti.load_data(data, variable_manager=variable_manager, loader=loader)
|
||||||
|
|
||||||
|
# Validate options
|
||||||
|
my_arg_names = frozenset(task.args.keys())
|
||||||
|
|
||||||
|
# validate bad args, otherwise we silently ignore
|
||||||
|
bad_opts = my_arg_names.difference(TaskInclude.VALID_ARGS)
|
||||||
|
if bad_opts and task.action in ('include_tasks', 'import_tasks'):
|
||||||
|
raise AnsibleParserError('Invalid options for %s: %s' % (task.action, ','.join(list(bad_opts))), obj=data)
|
||||||
|
|
||||||
|
if not task.args.get('_raw_params'):
|
||||||
|
task.args['_raw_params'] = task.args.pop('file')
|
||||||
|
|
||||||
|
apply_attrs = task.args.pop('apply', {})
|
||||||
|
if apply_attrs and task.action != 'include_tasks':
|
||||||
|
raise AnsibleParserError('Invalid options for %s: apply' % task.action, obj=data)
|
||||||
|
elif apply_attrs:
|
||||||
|
apply_attrs['block'] = []
|
||||||
|
p_block = Block.load(
|
||||||
|
apply_attrs,
|
||||||
|
play=block._play,
|
||||||
|
parent_block=block,
|
||||||
|
role=role,
|
||||||
|
task_include=task_include,
|
||||||
|
use_handlers=block._use_handlers,
|
||||||
|
variable_manager=variable_manager,
|
||||||
|
loader=loader,
|
||||||
|
)
|
||||||
|
task._parent = p_block
|
||||||
|
|
||||||
|
return task
|
||||||
|
|
||||||
def copy(self, exclude_parent=False, exclude_tasks=False):
|
def copy(self, exclude_parent=False, exclude_tasks=False):
|
||||||
new_me = super(TaskInclude, self).copy(exclude_parent=exclude_parent, exclude_tasks=exclude_tasks)
|
new_me = super(TaskInclude, self).copy(exclude_parent=exclude_parent, exclude_tasks=exclude_tasks)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
- hosts: testhost
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- import_tasks:
|
||||||
|
file: import_tasks.yml
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- foo
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- include_tasks_result is defined
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- import_role:
|
||||||
|
name: import_role
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- foo
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- include_role_result is defined
|
||||||
|
tags:
|
||||||
|
- always
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
- hosts: testhost
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- include_tasks:
|
||||||
|
file: include_tasks.yml
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- foo
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- include_tasks_result is defined
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- include_role:
|
||||||
|
name: include_role
|
||||||
|
apply:
|
||||||
|
tags:
|
||||||
|
- foo
|
||||||
|
tags:
|
||||||
|
- always
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- include_role_result is defined
|
||||||
|
tags:
|
||||||
|
- always
|
|
@ -0,0 +1,2 @@
|
||||||
|
- set_fact:
|
||||||
|
include_tasks_result: true
|
|
@ -0,0 +1,2 @@
|
||||||
|
- set_fact:
|
||||||
|
include_role_result: true
|
|
@ -67,3 +67,13 @@ ANSIBLE_STRATEGY='free' ansible-playbook undefined_var/playbook.yml -i ../../in
|
||||||
# Include path inheritance using host var for include file path
|
# Include path inheritance using host var for include file path
|
||||||
ANSIBLE_STRATEGY='linear' ansible-playbook include_path_inheritance/playbook.yml -i ../../inventory "$@"
|
ANSIBLE_STRATEGY='linear' ansible-playbook include_path_inheritance/playbook.yml -i ../../inventory "$@"
|
||||||
ANSIBLE_STRATEGY='free' ansible-playbook include_path_inheritance/playbook.yml -i ../../inventory "$@"
|
ANSIBLE_STRATEGY='free' ansible-playbook include_path_inheritance/playbook.yml -i ../../inventory "$@"
|
||||||
|
|
||||||
|
# include_ + apply (explicit inheritance)
|
||||||
|
ANSIBLE_STRATEGY='linear' ansible-playbook apply/include_apply.yml -i ../../inventory "$@" --tags foo
|
||||||
|
set +e
|
||||||
|
OUT=$(ANSIBLE_STRATEGY='linear' ansible-playbook apply/import_apply.yml -i ../../inventory "$@" --tags foo 2>&1 | grep 'ERROR! Invalid options for import_tasks: apply')
|
||||||
|
set -e
|
||||||
|
if [[ -z "$OUT" ]]; then
|
||||||
|
echo "apply on import_tasks did not cause error"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
Loading…
Reference in a new issue