Make the loop variable (item by default) settable per task

Required for include+with* tasks which may include files that also
have tasks containing a with* loop.

Fixes #12736
This commit is contained in:
James Cammarata 2015-10-23 03:27:09 -04:00
commit 6eefc11c39
11 changed files with 151 additions and 36 deletions

View file

@ -23,7 +23,7 @@ from copy import deepcopy
class Attribute:
def __init__(self, isa=None, private=False, default=None, required=False, listof=None, priority=0, always_post_validate=False):
def __init__(self, isa=None, private=False, default=None, required=False, listof=None, priority=0, class_type=None, always_post_validate=False):
self.isa = isa
self.private = private
@ -31,6 +31,7 @@ class Attribute:
self.required = required
self.listof = listof
self.priority = priority
self.class_type = class_type
self.always_post_validate = always_post_validate
if default is not None and self.isa in ('list', 'dict', 'set'):

View file

@ -304,6 +304,8 @@ class Base:
method = getattr(self, '_post_validate_%s' % name, None)
if method:
value = method(attribute, getattr(self, name), templar)
elif attribute.isa == 'class':
value = getattr(self, name)
else:
# if the attribute contains a variable, template it now
value = templar.template(getattr(self, name))
@ -363,6 +365,10 @@ class Base:
value = dict()
elif not isinstance(value, dict):
raise TypeError("%s is not a dictionary" % value)
elif attribute.isa == 'class':
if not isinstance(value, attribute.class_type):
raise TypeError("%s is not a valid %s (got a %s instead)" % (name, attribute.class_type, type(value)))
value.post_validate(templar=templar)
# and assign the massaged value back to the attribute field
setattr(self, name, value)

View file

@ -80,8 +80,11 @@ class IncludedFile:
templar = Templar(loader=loader, variables=task_vars)
include_variables = include_result.get('include_variables', dict())
if 'item' in include_result:
task_vars['item'] = include_variables['item'] = include_result['item']
loop_var = 'item'
if res._task.loop_control:
loop_var = res._task.loop_control.loop_var or 'item'
if loop_var in include_result:
task_vars[loop_var] = include_variables[loop_var] = include_result[loop_var]
if original_task:
if original_task.static:

View file

@ -0,0 +1,40 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import itertools
from ansible.compat.six import string_types
from ansible.errors import AnsibleError
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base
class LoopControl(Base):
_loop_var = FieldAttribute(isa='str')
def __init__(self):
super(LoopControl, self).__init__()
@staticmethod
def load(data, variable_manager=None, loader=None):
t = LoopControl()
return t.load_data(data, variable_manager=variable_manager, loader=loader)

View file

@ -32,6 +32,7 @@ from ansible.playbook.base import Base
from ansible.playbook.become import Become
from ansible.playbook.block import Block
from ansible.playbook.conditional import Conditional
from ansible.playbook.loop_control import LoopControl
from ansible.playbook.role import Role
from ansible.playbook.taggable import Taggable
@ -78,6 +79,7 @@ class Task(Base, Conditional, Taggable, Become):
_first_available_file = FieldAttribute(isa='list')
_loop = FieldAttribute(isa='string', private=True)
_loop_args = FieldAttribute(isa='list', private=True)
_loop_control = FieldAttribute(isa='class', class_type=LoopControl)
_name = FieldAttribute(isa='string', default='')
_notify = FieldAttribute(isa='list')
_poll = FieldAttribute(isa='int')
@ -220,6 +222,16 @@ class Task(Base, Conditional, Taggable, Become):
return super(Task, self).preprocess_data(new_ds)
def _load_loop_control(self, attr, ds):
if not isinstance(ds, dict):
raise AnsibleParserError(
"the `loop_control` value must be specified as a dictionary and cannot " \
"be a variable itself (though it can contain variables)",
obj=ds,
)
return LoopControl.load(data=ds, variable_manager=self._variable_manager, loader=self._loader)
def post_validate(self, templar):
'''
Override of base class post_validate, to also do final validation on