mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-27 23:21:22 -07:00
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:
parent
ff0296f98a
commit
6eefc11c39
11 changed files with 151 additions and 36 deletions
|
@ -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'):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
40
lib/ansible/playbook/loop_control.py
Normal file
40
lib/ansible/playbook/loop_control.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue