mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Adding v2 task/block iterator and some reorganizing
This commit is contained in:
parent
5a4a212869
commit
24bebd85b4
25 changed files with 358 additions and 49 deletions
|
@ -1,36 +0,0 @@
|
||||||
# (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
|
|
||||||
|
|
||||||
class HostPlaybookIterator:
|
|
||||||
|
|
||||||
def __init__(self, host, playbook):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_next_task(self):
|
|
||||||
assert False
|
|
||||||
|
|
||||||
def is_blocked(self):
|
|
||||||
# depending on strategy, either
|
|
||||||
# ‘linear’ -- all prev tasks must be completed for all hosts
|
|
||||||
# ‘free’ -- this host doesn’t have any more work to do
|
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
97
v2/ansible/executor/playbook_iterator.py
Normal file
97
v2/ansible/executor/playbook_iterator.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# (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
|
||||||
|
|
||||||
|
class PlaybookState:
|
||||||
|
|
||||||
|
'''
|
||||||
|
A helper class, which keeps track of the task iteration
|
||||||
|
state for a given playbook. This is used in the PlaybookIterator
|
||||||
|
class on a per-host basis.
|
||||||
|
'''
|
||||||
|
def __init__(self, parent_iterator):
|
||||||
|
self._parent_iterator = parent_iterator
|
||||||
|
self._cur_play = 0
|
||||||
|
self._task_list = None
|
||||||
|
self._cur_task_pos = 0
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
'''
|
||||||
|
Determines and returns the next available task from the playbook,
|
||||||
|
advancing through the list of plays as it goes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# when we hit the end of the playbook entries list, we return
|
||||||
|
# None to indicate we're there
|
||||||
|
if self._cur_play > len(self._parent_iterator._playbook._entries) - 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# initialize the task list by calling the .compile() method
|
||||||
|
# on the play, which will call compile() for all child objects
|
||||||
|
if self._task_list is None:
|
||||||
|
self._task_list = self._parent_iterator._playbook._entries[self._cur_play].compile()
|
||||||
|
|
||||||
|
# if we've hit the end of this plays task list, move on to the next
|
||||||
|
# and reset the position values for the next iteration
|
||||||
|
if self._cur_task_pos > len(self._task_list) - 1:
|
||||||
|
self._cur_play += 1
|
||||||
|
self._task_list = None
|
||||||
|
self._cur_task_pos = 0
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# FIXME: do tag/conditional evaluation here and advance
|
||||||
|
# the task position if it should be skipped without
|
||||||
|
# returning a task
|
||||||
|
task = self._task_list[self._cur_task_pos]
|
||||||
|
self._cur_task_pos += 1
|
||||||
|
|
||||||
|
# Skip the task if it is the member of a role which has already
|
||||||
|
# been run, unless the role allows multiple executions
|
||||||
|
if task._role:
|
||||||
|
# FIXME: this should all be done via member functions
|
||||||
|
# instead of direct access to internal variables
|
||||||
|
if task._role.has_run() and not task._role._metadata._allow_duplicates:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return task
|
||||||
|
|
||||||
|
class PlaybookIterator:
|
||||||
|
|
||||||
|
'''
|
||||||
|
The main iterator class, which keeps the state of the playbook
|
||||||
|
on a per-host basis using the above PlaybookState class.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, inventory, log_manager, playbook):
|
||||||
|
self._playbook = playbook
|
||||||
|
self._log_manager = log_manager
|
||||||
|
self._host_entries = dict()
|
||||||
|
|
||||||
|
# build the per-host dictionary of playbook states
|
||||||
|
for host in inventory.get_hosts():
|
||||||
|
self._host_entries[host.get_name()] = PlaybookState(parent_iterator=self)
|
||||||
|
|
||||||
|
def get_next_task_for_host(self, host):
|
||||||
|
''' fetch the next task for the given host '''
|
||||||
|
if host.get_name() not in self._host_entries:
|
||||||
|
raise AnsibleError("invalid host specified for playbook iteration")
|
||||||
|
|
||||||
|
return self._host_entries[host.get_name()].next()
|
|
@ -148,6 +148,10 @@ class DataLoader():
|
||||||
|
|
||||||
raise AnsibleParserError(YAML_SYNTAX_ERROR, obj=err_obj, show_content=show_content)
|
raise AnsibleParserError(YAML_SYNTAX_ERROR, obj=err_obj, show_content=show_content)
|
||||||
|
|
||||||
|
def get_basedir(self):
|
||||||
|
''' returns the current basedir '''
|
||||||
|
return self._basedir
|
||||||
|
|
||||||
def set_basedir(self, basedir):
|
def set_basedir(self, basedir):
|
||||||
''' sets the base directory, used to find files when a relative path is given '''
|
''' sets the base directory, used to find files when a relative path is given '''
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,9 @@ class Playbook:
|
||||||
basedir = os.path.dirname(file_name)
|
basedir = os.path.dirname(file_name)
|
||||||
self._loader.set_basedir(basedir)
|
self._loader.set_basedir(basedir)
|
||||||
|
|
||||||
|
# also add the basedir to the list of module directories
|
||||||
|
push_basedir(basedir)
|
||||||
|
|
||||||
ds = self._loader.load_from_file(file_name)
|
ds = self._loader.load_from_file(file_name)
|
||||||
if not isinstance(ds, list):
|
if not isinstance(ds, list):
|
||||||
raise AnsibleParserError("playbooks must be a list of plays", obj=ds)
|
raise AnsibleParserError("playbooks must be a list of plays", obj=ds)
|
||||||
|
@ -75,4 +78,5 @@ class Playbook:
|
||||||
|
|
||||||
self._entries.append(entry_obj)
|
self._entries.append(entry_obj)
|
||||||
|
|
||||||
|
def get_entries(self):
|
||||||
|
return self._entries[:]
|
||||||
|
|
|
@ -22,6 +22,7 @@ __metaclass__ = type
|
||||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||||
from ansible.playbook.base import Base
|
from ansible.playbook.base import Base
|
||||||
from ansible.playbook.helpers import load_list_of_tasks
|
from ansible.playbook.helpers import load_list_of_tasks
|
||||||
|
from ansible.playbook.task_include import TaskInclude
|
||||||
|
|
||||||
class Block(Base):
|
class Block(Base):
|
||||||
|
|
||||||
|
@ -35,8 +36,10 @@ class Block(Base):
|
||||||
# similar to the 'else' clause for exceptions
|
# similar to the 'else' clause for exceptions
|
||||||
#_otherwise = FieldAttribute(isa='list')
|
#_otherwise = FieldAttribute(isa='list')
|
||||||
|
|
||||||
def __init__(self, role=None):
|
def __init__(self, parent_block=None, role=None, task_include=None):
|
||||||
self.role = role
|
self._parent_block = parent_block
|
||||||
|
self._role = role
|
||||||
|
self._task_include = task_include
|
||||||
super(Block, self).__init__()
|
super(Block, self).__init__()
|
||||||
|
|
||||||
def get_variables(self):
|
def get_variables(self):
|
||||||
|
@ -45,8 +48,8 @@ class Block(Base):
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(data, role=None, loader=None):
|
def load(data, parent_block=None, role=None, task_include=None, loader=None):
|
||||||
b = Block(role=role)
|
b = Block(parent_block=parent_block, role=role, task_include=task_include)
|
||||||
return b.load_data(data, loader=loader)
|
return b.load_data(data, loader=loader)
|
||||||
|
|
||||||
def munge(self, ds):
|
def munge(self, ds):
|
||||||
|
@ -79,3 +82,14 @@ class Block(Base):
|
||||||
#def _load_otherwise(self, attr, ds):
|
#def _load_otherwise(self, attr, ds):
|
||||||
# return self._load_list_of_tasks(ds, block=self, loader=self._loader)
|
# return self._load_list_of_tasks(ds, block=self, loader=self._loader)
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
'''
|
||||||
|
Returns the task list for this object
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
for task in self.block:
|
||||||
|
# FIXME: evaulate task tags/conditionals here
|
||||||
|
task_list.extend(task.compile())
|
||||||
|
|
||||||
|
return task_list
|
||||||
|
|
|
@ -15,11 +15,16 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from types import NoneType
|
from types import NoneType
|
||||||
|
|
||||||
from ansible.errors import AnsibleParserError
|
from ansible.errors import AnsibleParserError
|
||||||
|
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
|
||||||
|
|
||||||
def load_list_of_blocks(ds, role=None, loader=None):
|
|
||||||
|
def load_list_of_blocks(ds, parent_block=None, role=None, task_include=None, loader=None):
|
||||||
'''
|
'''
|
||||||
Given a list of mixed task/block data (parsed from YAML),
|
Given a list of mixed task/block data (parsed from YAML),
|
||||||
return a list of Block() objects, where implicit blocks
|
return a list of Block() objects, where implicit blocks
|
||||||
|
@ -34,7 +39,7 @@ def load_list_of_blocks(ds, role=None, loader=None):
|
||||||
block_list = []
|
block_list = []
|
||||||
if ds:
|
if ds:
|
||||||
for block in ds:
|
for block in ds:
|
||||||
b = Block.load(block, role=role, loader=loader)
|
b = Block.load(block, parent_block=parent_block, role=role, task_include=task_include, loader=loader)
|
||||||
block_list.append(b)
|
block_list.append(b)
|
||||||
|
|
||||||
return block_list
|
return block_list
|
||||||
|
@ -58,7 +63,17 @@ def load_list_of_tasks(ds, block=None, role=None, task_include=None, loader=None
|
||||||
raise AnsibleParserError("task/handler entries must be dictionaries (got a %s)" % type(task), obj=ds)
|
raise AnsibleParserError("task/handler entries must be dictionaries (got a %s)" % type(task), obj=ds)
|
||||||
|
|
||||||
if 'include' in task:
|
if 'include' in task:
|
||||||
|
cur_basedir = None
|
||||||
|
if isinstance(task, AnsibleBaseYAMLObject) and loader:
|
||||||
|
pos_info = task.get_position_info()
|
||||||
|
new_basedir = os.path.dirname(pos_info[0])
|
||||||
|
cur_basedir = loader.get_basedir()
|
||||||
|
loader.set_basedir(new_basedir)
|
||||||
|
|
||||||
t = TaskInclude.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
t = TaskInclude.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
||||||
|
|
||||||
|
if cur_basedir and loader:
|
||||||
|
loader.set_basedir(cur_basedir)
|
||||||
else:
|
else:
|
||||||
t = Task.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
t = Task.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
||||||
|
|
||||||
|
@ -85,3 +100,15 @@ def load_list_of_roles(ds, loader=None):
|
||||||
|
|
||||||
return roles
|
return roles
|
||||||
|
|
||||||
|
def compile_block_list(block_list):
|
||||||
|
'''
|
||||||
|
Given a list of blocks, compile them into a flat list of tasks
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
|
||||||
|
for block in block_list:
|
||||||
|
task_list.extend(block.compile())
|
||||||
|
|
||||||
|
return task_list
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,8 @@ from ansible.parsing.yaml import DataLoader
|
||||||
|
|
||||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||||
from ansible.playbook.base import Base
|
from ansible.playbook.base import Base
|
||||||
from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles
|
from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles, compile_block_list
|
||||||
|
from ansible.playbook.role import Role
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Play']
|
__all__ = ['Play']
|
||||||
|
@ -155,3 +156,41 @@ class Play(Base):
|
||||||
return load_list_of_roles(ds, loader=self._loader)
|
return load_list_of_roles(ds, loader=self._loader)
|
||||||
|
|
||||||
# FIXME: post_validation needs to ensure that su/sudo are not both set
|
# FIXME: post_validation needs to ensure that su/sudo are not both set
|
||||||
|
|
||||||
|
def _compile_roles(self):
|
||||||
|
'''
|
||||||
|
Handles the role compilation step, returning a flat list of tasks
|
||||||
|
with the lowest level dependencies first. For example, if a role R
|
||||||
|
has a dependency D1, which also has a dependency D2, the tasks from
|
||||||
|
D2 are merged first, followed by D1, and lastly by the tasks from
|
||||||
|
the parent role R last. This is done for all roles in the Play.
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
|
||||||
|
if len(self.roles) > 0:
|
||||||
|
for ri in self.roles:
|
||||||
|
# The internal list of roles are actualy RoleInclude objects,
|
||||||
|
# so we load the role from that now
|
||||||
|
role = Role.load(ri)
|
||||||
|
|
||||||
|
# FIXME: evauluate conditional of roles here?
|
||||||
|
task_list.extend(role.compile())
|
||||||
|
|
||||||
|
return task_list
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
'''
|
||||||
|
Compiles and returns the task list for this play, compiled from the
|
||||||
|
roles (which are themselves compiled recursively) and/or the list of
|
||||||
|
tasks specified in the play.
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
|
||||||
|
task_list.extend(compile_block_list(self.pre_tasks))
|
||||||
|
task_list.extend(self._compile_roles())
|
||||||
|
task_list.extend(compile_block_list(self.tasks))
|
||||||
|
task_list.extend(compile_block_list(self.post_tasks))
|
||||||
|
|
||||||
|
return task_list
|
||||||
|
|
|
@ -30,7 +30,7 @@ from ansible.errors import AnsibleError, AnsibleParserError
|
||||||
from ansible.parsing.yaml import DataLoader
|
from ansible.parsing.yaml import DataLoader
|
||||||
from ansible.playbook.attribute import FieldAttribute
|
from ansible.playbook.attribute import FieldAttribute
|
||||||
from ansible.playbook.base import Base
|
from ansible.playbook.base import Base
|
||||||
from ansible.playbook.helpers import load_list_of_blocks
|
from ansible.playbook.helpers import load_list_of_blocks, compile_block_list
|
||||||
from ansible.playbook.role.include import RoleInclude
|
from ansible.playbook.role.include import RoleInclude
|
||||||
from ansible.playbook.role.metadata import RoleMetadata
|
from ansible.playbook.role.metadata import RoleMetadata
|
||||||
|
|
||||||
|
@ -87,6 +87,10 @@ class Role:
|
||||||
if parent_role:
|
if parent_role:
|
||||||
self.add_parent(parent_role)
|
self.add_parent(parent_role)
|
||||||
|
|
||||||
|
# save the current base directory for the loader and set it to the current role path
|
||||||
|
cur_basedir = self._loader.get_basedir()
|
||||||
|
self._loader.set_basedir(self._role_path)
|
||||||
|
|
||||||
# load the role's files, if they exist
|
# load the role's files, if they exist
|
||||||
metadata = self._load_role_yaml('meta')
|
metadata = self._load_role_yaml('meta')
|
||||||
if metadata:
|
if metadata:
|
||||||
|
@ -110,6 +114,9 @@ class Role:
|
||||||
if not isinstance(self._default_vars, (dict, NoneType)):
|
if not isinstance(self._default_vars, (dict, NoneType)):
|
||||||
raise AnsibleParserError("The default/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name, obj=ds)
|
raise AnsibleParserError("The default/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name, obj=ds)
|
||||||
|
|
||||||
|
# and finally restore the previous base directory
|
||||||
|
self._loader.set_basedir(cur_basedir)
|
||||||
|
|
||||||
def _load_role_yaml(self, subdir):
|
def _load_role_yaml(self, subdir):
|
||||||
file_path = os.path.join(self._role_path, subdir)
|
file_path = os.path.join(self._role_path, subdir)
|
||||||
if self._loader.path_exists(file_path) and self._loader.is_directory(file_path):
|
if self._loader.path_exists(file_path) and self._loader.is_directory(file_path):
|
||||||
|
@ -186,3 +193,26 @@ class Role:
|
||||||
|
|
||||||
return direct_deps + child_deps
|
return direct_deps + child_deps
|
||||||
|
|
||||||
|
def get_task_blocks(self):
|
||||||
|
return self._task_blocks[:]
|
||||||
|
|
||||||
|
def get_handler_blocks(self):
|
||||||
|
return self._handler_blocks[:]
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
'''
|
||||||
|
Returns the task list for this role, which is created by first
|
||||||
|
recursively compiling the tasks for all direct dependencies, and
|
||||||
|
then adding on the tasks for this role.
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
|
||||||
|
deps = self.get_direct_dependencies()
|
||||||
|
for dep in deps:
|
||||||
|
task_list.extend(dep.compile())
|
||||||
|
|
||||||
|
task_list.extend(compile_block_list(self._task_blocks))
|
||||||
|
|
||||||
|
return task_list
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,7 @@ class RoleDefinition(Base):
|
||||||
# FIXME: make the parser smart about list/string entries
|
# FIXME: make the parser smart about list/string entries
|
||||||
# in the yaml so the error line/file can be reported
|
# in the yaml so the error line/file can be reported
|
||||||
# here
|
# here
|
||||||
|
|
||||||
raise AnsibleError("the role '%s' was not found" % role_name)
|
raise AnsibleError("the role '%s' was not found" % role_name)
|
||||||
|
|
||||||
def _split_role_params(self, ds):
|
def _split_role_params(self, ds):
|
||||||
|
|
|
@ -60,6 +60,7 @@ class Task(Base):
|
||||||
_delay = FieldAttribute(isa='int')
|
_delay = FieldAttribute(isa='int')
|
||||||
_delegate_to = FieldAttribute(isa='string')
|
_delegate_to = FieldAttribute(isa='string')
|
||||||
_environment = FieldAttribute(isa='dict')
|
_environment = FieldAttribute(isa='dict')
|
||||||
|
_failed_when = FieldAttribute(isa='string')
|
||||||
_first_available_file = FieldAttribute(isa='list')
|
_first_available_file = FieldAttribute(isa='list')
|
||||||
_ignore_errors = FieldAttribute(isa='bool')
|
_ignore_errors = FieldAttribute(isa='bool')
|
||||||
|
|
||||||
|
@ -179,3 +180,11 @@ class Task(Base):
|
||||||
|
|
||||||
return new_ds
|
return new_ds
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
'''
|
||||||
|
For tasks, this is just a dummy method returning an array
|
||||||
|
with 'self' in it, so we don't have to care about task types
|
||||||
|
further up the chain.
|
||||||
|
'''
|
||||||
|
|
||||||
|
return [self]
|
||||||
|
|
|
@ -24,7 +24,7 @@ from ansible.parsing.splitter import split_args, parse_kv
|
||||||
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
||||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||||
from ansible.playbook.base import Base
|
from ansible.playbook.base import Base
|
||||||
from ansible.playbook.helpers import load_list_of_tasks
|
from ansible.playbook.helpers import load_list_of_blocks, compile_block_list
|
||||||
from ansible.plugins import lookup_finder
|
from ansible.plugins import lookup_finder
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,11 +57,12 @@ class TaskInclude(Base):
|
||||||
_when = FieldAttribute(isa='list', default=[])
|
_when = FieldAttribute(isa='list', default=[])
|
||||||
|
|
||||||
def __init__(self, block=None, role=None, task_include=None):
|
def __init__(self, block=None, role=None, task_include=None):
|
||||||
self._tasks = []
|
|
||||||
self._block = block
|
self._block = block
|
||||||
self._role = role
|
self._role = role
|
||||||
self._task_include = task_include
|
self._task_include = task_include
|
||||||
|
|
||||||
|
self._task_blocks = []
|
||||||
|
|
||||||
super(TaskInclude, self).__init__()
|
super(TaskInclude, self).__init__()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -136,11 +137,27 @@ class TaskInclude(Base):
|
||||||
|
|
||||||
|
|
||||||
def _load_include(self, attr, ds):
|
def _load_include(self, attr, ds):
|
||||||
''' loads the file name specified in the ds and returns a list of tasks '''
|
''' loads the file name specified in the ds and returns a list of blocks '''
|
||||||
|
|
||||||
data = self._loader.load_from_file(ds)
|
data = self._loader.load_from_file(ds)
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
raise AnsibleParsingError("included task files must contain a list of tasks", obj=ds)
|
raise AnsibleParsingError("included task files must contain a list of tasks", obj=ds)
|
||||||
|
|
||||||
self._tasks = load_list_of_tasks(data, task_include=self, loader=self._loader)
|
self._task_blocks = load_list_of_blocks(
|
||||||
|
data,
|
||||||
|
parent_block=self._block,
|
||||||
|
task_include=self,
|
||||||
|
role=self._role,
|
||||||
|
loader=self._loader
|
||||||
|
)
|
||||||
return ds
|
return ds
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
'''
|
||||||
|
Returns the task list for the included tasks.
|
||||||
|
'''
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
task_list.extend(compile_block_list(self._task_blocks))
|
||||||
|
return task_list
|
||||||
|
|
||||||
|
|
83
v2/test/executor/test_playbook_iterator.py
Normal file
83
v2/test/executor/test_playbook_iterator.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# (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
|
||||||
|
|
||||||
|
from ansible.compat.tests import unittest
|
||||||
|
from ansible.compat.tests.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError, AnsibleParserError
|
||||||
|
from ansible.executor.playbook_iterator import PlaybookIterator
|
||||||
|
from ansible.playbook import Playbook
|
||||||
|
|
||||||
|
from test.mock.loader import DictDataLoader
|
||||||
|
|
||||||
|
class TestPlaybookIterator(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_playbook_iterator(self):
|
||||||
|
fake_loader = DictDataLoader({
|
||||||
|
"test_play.yml": """
|
||||||
|
- hosts: all
|
||||||
|
roles:
|
||||||
|
- test_role
|
||||||
|
pre_tasks:
|
||||||
|
- debug: msg="this is a pre_task"
|
||||||
|
tasks:
|
||||||
|
- debug: msg="this is a regular task"
|
||||||
|
post_tasks:
|
||||||
|
- debug: msg="this is a post_task"
|
||||||
|
""",
|
||||||
|
'/etc/ansible/roles/test_role/tasks/main.yml': """
|
||||||
|
- debug: msg="this is a role task"
|
||||||
|
""",
|
||||||
|
})
|
||||||
|
|
||||||
|
p = Playbook.load('test_play.yml', loader=fake_loader)
|
||||||
|
|
||||||
|
hosts = []
|
||||||
|
for i in range(0, 10):
|
||||||
|
host = MagicMock()
|
||||||
|
host.get_name.return_value = 'host%02d' % i
|
||||||
|
hosts.append(host)
|
||||||
|
|
||||||
|
inventory = MagicMock()
|
||||||
|
inventory.get_hosts.return_value = hosts
|
||||||
|
|
||||||
|
itr = PlaybookIterator(inventory, None, p)
|
||||||
|
task = itr.get_next_task_for_host(hosts[0])
|
||||||
|
print(task)
|
||||||
|
self.assertIsNotNone(task)
|
||||||
|
task = itr.get_next_task_for_host(hosts[0])
|
||||||
|
print(task)
|
||||||
|
self.assertIsNotNone(task)
|
||||||
|
task = itr.get_next_task_for_host(hosts[0])
|
||||||
|
print(task)
|
||||||
|
self.assertIsNotNone(task)
|
||||||
|
task = itr.get_next_task_for_host(hosts[0])
|
||||||
|
print(task)
|
||||||
|
self.assertIsNotNone(task)
|
||||||
|
task = itr.get_next_task_for_host(hosts[0])
|
||||||
|
print(task)
|
||||||
|
self.assertIsNone(task)
|
|
@ -75,3 +75,9 @@ class TestBlock(unittest.TestCase):
|
||||||
self.assertEqual(len(b.block), 1)
|
self.assertEqual(len(b.block), 1)
|
||||||
assert isinstance(b.block[0], Task)
|
assert isinstance(b.block[0], Task)
|
||||||
|
|
||||||
|
def test_block_compile(self):
|
||||||
|
ds = [dict(action='foo')]
|
||||||
|
b = Block.load(ds)
|
||||||
|
tasks = b.compile()
|
||||||
|
self.assertEqual(len(tasks), 1)
|
||||||
|
self.assertIsInstance(tasks[0], Task)
|
||||||
|
|
|
@ -117,4 +117,16 @@ class TestPlay(unittest.TestCase):
|
||||||
roles=['foo'],
|
roles=['foo'],
|
||||||
), loader=fake_loader)
|
), loader=fake_loader)
|
||||||
|
|
||||||
|
tasks = p.compile()
|
||||||
|
|
||||||
|
def test_play_compile(self):
|
||||||
|
p = Play.load(dict(
|
||||||
|
name="test play",
|
||||||
|
hosts=['foo'],
|
||||||
|
gather_facts=False,
|
||||||
|
tasks=[dict(action='shell echo "hello world"')],
|
||||||
|
))
|
||||||
|
|
||||||
|
tasks = p.compile()
|
||||||
|
self.assertEqual(len(tasks), 1)
|
||||||
|
self.assertIsInstance(tasks[0], Task)
|
||||||
|
|
|
@ -45,6 +45,7 @@ class TestPlaybook(unittest.TestCase):
|
||||||
""",
|
""",
|
||||||
})
|
})
|
||||||
p = Playbook.load("test_file.yml", loader=fake_loader)
|
p = Playbook.load("test_file.yml", loader=fake_loader)
|
||||||
|
entries = p.get_entries()
|
||||||
|
|
||||||
def test_bad_playbook_files(self):
|
def test_bad_playbook_files(self):
|
||||||
fake_loader = DictDataLoader({
|
fake_loader = DictDataLoader({
|
||||||
|
|
|
@ -45,6 +45,7 @@ class TestTaskInclude(unittest.TestCase):
|
||||||
|
|
||||||
def test_basic_task_include(self):
|
def test_basic_task_include(self):
|
||||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml'), loader=self._fake_loader)
|
ti = TaskInclude.load(AnsibleMapping(include='foo.yml'), loader=self._fake_loader)
|
||||||
|
tasks = ti.compile()
|
||||||
|
|
||||||
def test_task_include_with_loop(self):
|
def test_task_include_with_loop(self):
|
||||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', with_items=['a', 'b', 'c']), loader=self._fake_loader)
|
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', with_items=['a', 'b', 'c']), loader=self._fake_loader)
|
||||||
|
|
Loading…
Add table
Reference in a new issue