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:
Matt Martz 2018-05-31 12:08:38 -05:00 committed by GitHub
parent 961484e00d
commit da4ff18406
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 4 deletions

View file

@ -26,6 +26,10 @@ description:
- This module is also supported for Windows targets.
version_added: "2.2"
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:
description:
- The name of the role to be executed.
@ -89,6 +93,15 @@ EXAMPLES = """
include_role:
name: myrole
when: not idontwanttorun
- name: Apply tags to tasks within included file
include_role:
name: install
apply:
tags:
- install
tags:
- always
"""
RETURN = """

View file

@ -23,10 +23,20 @@ description:
- Includes a file with a list of tasks to be executed in the current playbook.
version_added: "2.4"
options:
free-form:
file:
description:
- 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.
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:
- 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
include_tasks: "{{ hostvar }}.yaml"
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 = """

View file

@ -23,6 +23,7 @@ from os.path import basename
from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.block import Block
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.role import Role
from ansible.playbook.role.include import RoleInclude
@ -45,7 +46,7 @@ class IncludeRole(TaskInclude):
BASE = ('name', 'role') # directly assigned
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
# =================================================================================
@ -134,6 +135,23 @@ class IncludeRole(TaskInclude):
from_key = key.replace('_from', '')
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.
for option in my_arg_names.intersection(IncludeRole.OTHER_ARGS):
setattr(ir, option, ir.args.get(option))

View file

@ -19,7 +19,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleParserError
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.block import Block
from ansible.playbook.task import Task
try:
@ -38,6 +40,10 @@ class TaskInclude(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
@ -49,8 +55,38 @@ class TaskInclude(Task):
@staticmethod
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)
return t.load_data(data, variable_manager=variable_manager, loader=loader)
ti = TaskInclude(block=block, role=role, task_include=task_include)
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):
new_me = super(TaskInclude, self).copy(exclude_parent=exclude_parent, exclude_tasks=exclude_tasks)

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
- set_fact:
include_tasks_result: true

View file

@ -0,0 +1,2 @@
- set_fact:
include_role_result: true

View file

@ -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
ANSIBLE_STRATEGY='linear' 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