V2 fixing bugs

pull/10601/head
James Cammarata 10 years ago
parent 79cf7e7292
commit 785c0c0c8c

@ -1,43 +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 HostLog:
def __init__(self, host):
self.host = host
def add_task_result(self, task_result):
pass
def has_failures(self):
assert False
def has_changes(self):
assert False
def get_tasks(self, are_executed=None, are_changed=None, are_successful=None):
assert False
def get_current_running_task(self)
# atomic decorator likely required?
assert False

@ -1,29 +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 HostLogManager:
def __init__(self):
pass
def get_log_for_host(self, host):
assert False

@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.errors import * from ansible.errors import *
from ansible.playbook.block import Block
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.utils.boolean import boolean from ansible.utils.boolean import boolean
@ -38,9 +39,10 @@ class HostState:
self.run_state = PlayIterator.ITERATING_SETUP self.run_state = PlayIterator.ITERATING_SETUP
self.fail_state = PlayIterator.FAILED_NONE self.fail_state = PlayIterator.FAILED_NONE
self.pending_setup = False self.pending_setup = False
self.child_state = None
def __repr__(self): def __repr__(self):
return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%d, fail_state=%d, pending_setup=%s" % ( return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%d, fail_state=%d, pending_setup=%s, child state? %s" % (
self.cur_block, self.cur_block,
self.cur_regular_task, self.cur_regular_task,
self.cur_rescue_task, self.cur_rescue_task,
@ -49,6 +51,7 @@ class HostState:
self.run_state, self.run_state,
self.fail_state, self.fail_state,
self.pending_setup, self.pending_setup,
self.child_state,
) )
def get_current_block(self): def get_current_block(self):
@ -64,6 +67,7 @@ class HostState:
new_state.run_state = self.run_state new_state.run_state = self.run_state
new_state.fail_state = self.fail_state new_state.fail_state = self.fail_state
new_state.pending_setup = self.pending_setup new_state.pending_setup = self.pending_setup
new_state.child_state = self.child_state
return new_state return new_state
class PlayIterator: class PlayIterator:
@ -104,75 +108,35 @@ class PlayIterator:
except KeyError: except KeyError:
raise AnsibleError("invalid host (%s) specified for playbook iteration" % host) raise AnsibleError("invalid host (%s) specified for playbook iteration" % host)
def get_next_task_for_host(self, host, peek=False, lock_step=True): def get_next_task_for_host(self, host, peek=False):
s = self.get_host_state(host) s = self.get_host_state(host)
task = None task = None
if s.run_state == self.ITERATING_COMPLETE: if s.run_state == self.ITERATING_COMPLETE:
return None return None
else: elif s.run_state == self.ITERATING_SETUP:
while True:
try:
cur_block = s._blocks[s.cur_block]
except IndexError:
s.run_state = self.ITERATING_COMPLETE
break
if s.run_state == self.ITERATING_SETUP:
s.run_state = self.ITERATING_TASKS s.run_state = self.ITERATING_TASKS
if self._play._gather_facts == 'smart' and not host.gathered_facts or boolean(self._play._gather_facts): s.pending_setup = True
if self._play.gather_facts == 'smart' and not host._gathered_facts or boolean(self._play.gather_facts):
if not peek:
# mark the host as having gathered facts # mark the host as having gathered facts
host.set_gathered_facts(True) host.set_gathered_facts(True)
task = Task() task = Task()
task.action = 'setup' task.action = 'setup'
task.args = {}
task.set_loader(self._play._loader) task.set_loader(self._play._loader)
else:
elif s.run_state == self.ITERATING_TASKS:
# clear the pending setup flag, since we're past that and it didn't fail
if s.pending_setup:
s.pending_setup = False s.pending_setup = False
if s.fail_state & self.FAILED_TASKS == self.FAILED_TASKS: if not task:
s.run_state = self.ITERATING_RESCUE (s, task) = self._get_next_task_from_state(s, peek=peek)
elif s.cur_regular_task >= len(cur_block.block):
s.run_state = self.ITERATING_ALWAYS
else:
task = cur_block.block[s.cur_regular_task]
s.cur_regular_task += 1
break
elif s.run_state == self.ITERATING_RESCUE:
if s.fail_state & self.FAILED_RESCUE == self.FAILED_RESCUE:
s.run_state = self.ITERATING_ALWAYS
elif s.cur_rescue_task >= len(cur_block.rescue):
if len(cur_block.rescue) > 0:
s.fail_state = self.FAILED_NONE
s.run_state = self.ITERATING_ALWAYS
else:
task = cur_block.rescue[s.cur_rescue_task]
s.cur_rescue_task += 1
break
elif s.run_state == self.ITERATING_ALWAYS:
if s.cur_always_task >= len(cur_block.always):
if s.fail_state != self.FAILED_NONE:
s.run_state = self.ITERATING_COMPLETE
break
else:
s.cur_block += 1
s.cur_regular_task = 0
s.cur_rescue_task = 0
s.cur_always_task = 0
s.run_state = self.ITERATING_TASKS
else:
task= cur_block.always[s.cur_always_task]
s.cur_always_task += 1
break
if task and task._role: if task and task._role:
# if we had a current role, mark that role as completed # if we had a current role, mark that role as completed
if s.cur_role and task._role != s.cur_role and s.cur_role._had_task_run and not peek: if s.cur_role and task._role != s.cur_role and s.cur_role._had_task_run and not peek:
s.cur_role._completed = True s.cur_role._completed = True
s.cur_role = task._role s.cur_role = task._role
if not peek: if not peek:
@ -180,6 +144,86 @@ class PlayIterator:
return (s, task) return (s, task)
def _get_next_task_from_state(self, state, peek):
task = None
# if we previously encountered a child block and we have a
# saved child state, try and get the next task from there
if state.child_state:
(state.child_state, task) = self._get_next_task_from_state(state.child_state, peek=peek)
if task:
return (state.child_state, task)
else:
state.child_state = None
# try and find the next task, given the current state.
while True:
# try to get the current block from the list of blocks, and
# if we run past the end of the list we know we're done with
# this block
try:
block = state._blocks[state.cur_block]
except IndexError:
state.run_state = self.ITERATING_COMPLETE
return (state, None)
if state.run_state == self.ITERATING_TASKS:
# clear the pending setup flag, since we're past that and it didn't fail
if state.pending_setup:
state.pending_setup = False
if state.fail_state & self.FAILED_TASKS == self.FAILED_TASKS:
state.run_state = self.ITERATING_RESCUE
elif state.cur_regular_task >= len(block.block):
state.run_state = self.ITERATING_ALWAYS
else:
task = block.block[state.cur_regular_task]
state.cur_regular_task += 1
elif state.run_state == self.ITERATING_RESCUE:
if state.fail_state & self.FAILED_RESCUE == self.FAILED_RESCUE:
state.run_state = self.ITERATING_ALWAYS
elif state.cur_rescue_task >= len(block.rescue):
if len(block.rescue) > 0:
state.fail_state = self.FAILED_NONE
state.run_state = self.ITERATING_ALWAYS
else:
task = block.rescue[state.cur_rescue_task]
state.cur_rescue_task += 1
elif state.run_state == self.ITERATING_ALWAYS:
if state.cur_always_task >= len(block.always):
if state.fail_state != self.FAILED_NONE:
state.run_state = self.ITERATING_COMPLETE
else:
state.cur_block += 1
state.cur_regular_task = 0
state.cur_rescue_task = 0
state.cur_always_task = 0
state.run_state = self.ITERATING_TASKS
state.child_state = None
else:
task = block.always[state.cur_always_task]
state.cur_always_task += 1
elif state.run_state == self.ITERATING_COMPLETE:
return (state, None)
# if the current task is actually a child block, we dive into it
if isinstance(task, Block):
state.child_state = HostState(blocks=[task])
state.child_state.run_state = self.ITERATING_TASKS
state.child_state.cur_role = state.cur_role
(state.child_state, task) = self._get_next_task_from_state(state.child_state, peek=peek)
# if something above set the task, break out of the loop now
if task:
break
return (state, task)
def mark_host_failed(self, host): def mark_host_failed(self, host):
s = self.get_host_state(host) s = self.get_host_state(host)
if s.pending_setup: if s.pending_setup:
@ -206,25 +250,41 @@ class PlayIterator:
the different processes, and not all data structures are preserved. This method the different processes, and not all data structures are preserved. This method
allows us to find the original task passed into the executor engine. allows us to find the original task passed into the executor engine.
''' '''
s = self.get_host_state(host) def _search_block(block, task):
for block in s._blocks:
if block.block:
for t in block.block: for t in block.block:
if t._uuid == task._uuid: if isinstance(t, Block):
res = _search_block(t, task)
if res:
return res
elif t._uuid == task._uuid:
return t return t
if block.rescue:
for t in block.rescue: for t in block.rescue:
if t._uuid == task._uuid: if isinstance(t, Block):
res = _search_block(t, task)
if res:
return res
elif t._uuid == task._uuid:
return t return t
if block.always:
for t in block.always: for t in block.always:
if t._uuid == task._uuid: if isinstance(t, Block):
res = _search_block(t, task)
if res:
return res
elif t._uuid == task._uuid:
return t return t
return None return None
s = self.get_host_state(host)
for block in s._blocks:
res = _search_block(block, task)
if res:
return res
return None
def add_tasks(self, host, task_list): def add_tasks(self, host, task_list):
s = self.get_host_state(host) s = self.get_host_state(host)
target_block = s._blocks[s.cur_block].copy() target_block = s._blocks[s.cur_block].copy(exclude_parent=True)
if s.run_state == self.ITERATING_TASKS: if s.run_state == self.ITERATING_TASKS:
before = target_block.block[:s.cur_regular_task] before = target_block.block[:s.cur_regular_task]

@ -26,6 +26,7 @@ from ansible.errors import *
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook import Playbook from ansible.playbook import Playbook
from ansible.utils.color import colorize, hostcolor
from ansible.utils.debug import debug from ansible.utils.debug import debug
class PlaybookExecutor: class PlaybookExecutor:
@ -70,8 +71,8 @@ class PlaybookExecutor:
for batch in self._get_serialized_batches(new_play): for batch in self._get_serialized_batches(new_play):
if len(batch) == 0: if len(batch) == 0:
self._tqm._callback.playbook_on_play_start(new_play.name) self._tqm.send_callback('v2_playbook_on_play_start', new_play)
self._tqm._callback.playbook_on_no_hosts_matched() self._tqm.send_callback('v2_playbook_on_no_hosts_matched')
result = 0 result = 0
break break
# restrict the inventory to the hosts in the serialized batch # restrict the inventory to the hosts in the serialized batch
@ -90,6 +91,36 @@ class PlaybookExecutor:
raise raise
self._cleanup() self._cleanup()
# FIXME: this stat summary stuff should be cleaned up and moved
# to a new method, if it even belongs here...
self._tqm._display.banner("PLAY RECAP")
hosts = sorted(self._tqm._stats.processed.keys())
for h in hosts:
t = self._tqm._stats.summarize(h)
self._tqm._display.display("%s : %s %s %s %s" % (
hostcolor(h, t),
colorize('ok', t['ok'], 'green'),
colorize('changed', t['changed'], 'yellow'),
colorize('unreachable', t['unreachable'], 'red'),
colorize('failed', t['failures'], 'red')),
screen_only=True
)
self._tqm._display.display("%s : %s %s %s %s" % (
hostcolor(h, t, False),
colorize('ok', t['ok'], None),
colorize('changed', t['changed'], None),
colorize('unreachable', t['unreachable'], None),
colorize('failed', t['failures'], None)),
log_only=True
)
self._tqm._display.display("", screen_only=True)
# END STATS STUFF
return result return result
def _cleanup(self, signum=None, framenum=None): def _cleanup(self, signum=None, framenum=None):

@ -0,0 +1,51 @@
# (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 AggregateStats:
''' holds stats about per-host activity during playbook runs '''
def __init__(self):
self.processed = {}
self.failures = {}
self.ok = {}
self.dark = {}
self.changed = {}
self.skipped = {}
def increment(self, what, host):
''' helper function to bump a statistic '''
self.processed[host] = 1
prev = (getattr(self, what)).get(host, 0)
getattr(self, what)[host] = prev+1
def summarize(self, host):
''' return information about a particular host '''
return dict(
ok = self.ok.get(host, 0),
failures = self.failures.get(host, 0),
unreachable = self.dark.get(host,0),
changed = self.changed.get(host, 0),
skipped = self.skipped.get(host, 0)
)

@ -237,10 +237,14 @@ class TaskExecutor:
if self._task.poll > 0: if self._task.poll > 0:
result = self._poll_async_result(result=result) result = self._poll_async_result(result=result)
# update the local copy of vars with the registered value, if specified # update the local copy of vars with the registered value, if specified,
# or any facts which may have been generated by the module execution
if self._task.register: if self._task.register:
vars_copy[self._task.register] = result vars_copy[self._task.register] = result
if 'ansible_facts' in result:
vars_copy.update(result['ansible_facts'])
# create a conditional object to evaluate task conditions # create a conditional object to evaluate task conditions
cond = Conditional(loader=self._loader) cond = Conditional(loader=self._loader)
@ -266,6 +270,15 @@ class TaskExecutor:
if attempt < retries - 1: if attempt < retries - 1:
time.sleep(delay) time.sleep(delay)
# do the final update of the local variables here, for both registered
# values and any facts which may have been created
if self._task.register:
variables[self._task.register] = result
if 'ansible_facts' in result:
variables.update(result['ansible_facts'])
# and return
debug("attempt loop complete, returning result") debug("attempt loop complete, returning result")
return result return result

@ -29,9 +29,11 @@ from ansible.executor.connection_info import ConnectionInformation
from ansible.executor.play_iterator import PlayIterator from ansible.executor.play_iterator import PlayIterator
from ansible.executor.process.worker import WorkerProcess from ansible.executor.process.worker import WorkerProcess
from ansible.executor.process.result import ResultProcess from ansible.executor.process.result import ResultProcess
from ansible.executor.stats import AggregateStats
from ansible.plugins import callback_loader, strategy_loader from ansible.plugins import callback_loader, strategy_loader
from ansible.utils.debug import debug from ansible.utils.debug import debug
from ansible.utils.display import Display
__all__ = ['TaskQueueManager'] __all__ = ['TaskQueueManager']
@ -53,6 +55,9 @@ class TaskQueueManager:
self._variable_manager = variable_manager self._variable_manager = variable_manager
self._loader = loader self._loader = loader
self._options = options self._options = options
self._stats = AggregateStats()
self._display = Display()
# a special flag to help us exit cleanly # a special flag to help us exit cleanly
self._terminated = False self._terminated = False
@ -66,9 +71,14 @@ class TaskQueueManager:
self._final_q = multiprocessing.Queue() self._final_q = multiprocessing.Queue()
# FIXME: hard-coded the default callback plugin here, which # load all available callback plugins
# should be configurable. # FIXME: we need an option to white-list callback plugins
self._callback = callback_loader.get(callback) self._callback_plugins = []
for callback_plugin in callback_loader.all(class_only=True):
if hasattr(callback_plugin, 'CALLBACK_VERSION') and callback_plugin.CALLBACK_VERSION >= 2.0:
self._callback_plugins.append(callback_plugin(self._display))
else:
self._callback_plugins.append(callback_plugin())
# create the pool of worker threads, based on the number of forks specified # create the pool of worker threads, based on the number of forks specified
try: try:
@ -131,16 +141,11 @@ class TaskQueueManager:
''' '''
connection_info = ConnectionInformation(play, self._options) connection_info = ConnectionInformation(play, self._options)
self._callback.set_connection_info(connection_info) for callback_plugin in self._callback_plugins:
if hasattr(callback_plugin, 'set_connection_info'):
# run final validation on the play now, to make sure fields are templated callback_plugin.set_connection_info(connection_info)
# FIXME: is this even required? Everything is validated and merged at the
# task level, so else in the play needs to be templated
#all_vars = self._vmw.get_vars(loader=self._dlw, play=play)
#all_vars = self._vmw.get_vars(loader=self._loader, play=play)
#play.post_validate(all_vars=all_vars)
self._callback.playbook_on_play_start(play.name) self.send_callback('v2_playbook_on_play_start', play)
# initialize the shared dictionary containing the notified handlers # initialize the shared dictionary containing the notified handlers
self._initialize_notified_handlers(play.handlers) self._initialize_notified_handlers(play.handlers)
@ -172,9 +177,6 @@ class TaskQueueManager:
def get_inventory(self): def get_inventory(self):
return self._inventory return self._inventory
def get_callback(self):
return self._callback
def get_variable_manager(self): def get_variable_manager(self):
return self._variable_manager return self._variable_manager
@ -201,3 +203,18 @@ class TaskQueueManager:
def terminate(self): def terminate(self):
self._terminated = True self._terminated = True
def send_callback(self, method_name, *args, **kwargs):
for callback_plugin in self._callback_plugins:
# a plugin that set self.disabled to True will not be called
# see osx_say.py example for such a plugin
if getattr(callback_plugin, 'disabled', False):
continue
methods = [
getattr(callback_plugin, method_name, None),
getattr(callback_plugin, 'on_any', None)
]
for method in methods:
if method is not None:
method(*args, **kwargs)

@ -99,11 +99,14 @@ class DataLoader():
def path_exists(self, path): def path_exists(self, path):
return os.path.exists(path) return os.path.exists(path)
def is_file(self, path):
return os.path.isfile(path)
def is_directory(self, path): def is_directory(self, path):
return os.path.isdir(path) return os.path.isdir(path)
def is_file(self, path): def list_directory(self, path):
return os.path.isfile(path) return os.path.listdir(path)
def _safe_load(self, stream, file_name=None): def _safe_load(self, stream, file_name=None):
''' Implements yaml.safe_load(), except using our custom loader class. ''' ''' Implements yaml.safe_load(), except using our custom loader class. '''

@ -43,6 +43,7 @@ class Block(Base, Become, Conditional, Taggable):
self._task_include = task_include self._task_include = task_include
self._use_handlers = use_handlers self._use_handlers = use_handlers
self._dep_chain = [] self._dep_chain = []
self._vars = dict()
super(Block, self).__init__() super(Block, self).__init__()
@ -56,9 +57,12 @@ class Block(Base, Become, Conditional, Taggable):
if self._role: if self._role:
all_vars.update(self._role.get_vars()) all_vars.update(self._role.get_vars())
if self._parent_block:
all_vars.update(self._parent_block.get_vars())
if self._task_include: if self._task_include:
all_vars.update(self._task_include.get_vars()) all_vars.update(self._task_include.get_vars())
all_vars.update(self._vars)
return all_vars return all_vars
@staticmethod @staticmethod
@ -131,10 +135,14 @@ class Block(Base, Become, Conditional, Taggable):
# use_handlers=self._use_handlers, # use_handlers=self._use_handlers,
# ) # )
def copy(self): def copy(self, exclude_parent=False):
def _dupe_task_list(task_list, new_block): def _dupe_task_list(task_list, new_block):
new_task_list = [] new_task_list = []
for task in task_list: for task in task_list:
if isinstance(task, Block):
new_task = task.copy(exclude_parent=True)
new_task._parent_block = new_block
else:
new_task = task.copy(exclude_block=True) new_task = task.copy(exclude_block=True)
new_task._block = new_block new_task._block = new_block
new_task_list.append(new_task) new_task_list.append(new_task)
@ -149,7 +157,7 @@ class Block(Base, Become, Conditional, Taggable):
new_me.always = _dupe_task_list(self.always or [], new_me) new_me.always = _dupe_task_list(self.always or [], new_me)
new_me._parent_block = None new_me._parent_block = None
if self._parent_block: if self._parent_block and not exclude_parent:
new_me._parent_block = self._parent_block.copy() new_me._parent_block = self._parent_block.copy()
new_me._role = None new_me._role = None
@ -260,7 +268,7 @@ class Block(Base, Become, Conditional, Taggable):
value = self._attributes[attr] value = self._attributes[attr]
if not value: if not value:
if self._parent_block: if self._parent_block:
value = getattr(self._block, attr) value = getattr(self._parent_block, attr)
elif self._role: elif self._role:
value = getattr(self._role, attr) value = getattr(self._role, attr)
if not value and len(self._dep_chain): if not value and len(self._dep_chain):

@ -60,9 +60,9 @@ def load_list_of_tasks(ds, block=None, role=None, task_include=None, use_handler
''' '''
# we import here to prevent a circular dependency with imports # we import here to prevent a circular dependency with imports
from ansible.playbook.block import Block
from ansible.playbook.handler import Handler from ansible.playbook.handler import Handler
from ansible.playbook.task import Task from ansible.playbook.task import Task
#from ansible.playbook.task_include import TaskInclude
assert type(ds) == list assert type(ds) == list
@ -71,27 +71,17 @@ def load_list_of_tasks(ds, block=None, role=None, task_include=None, use_handler
if not isinstance(task, dict): if not isinstance(task, dict):
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 'block' in task:
# cur_basedir = None t = Block.load(
# if isinstance(task, AnsibleBaseYAMLObject) and loader: task,
# pos_info = task.get_position_info() parent_block=block,
# new_basedir = os.path.dirname(pos_info[0]) role=role,
# cur_basedir = loader.get_basedir() task_include=task_include,
# loader.set_basedir(new_basedir) use_handlers=use_handlers,
variable_manager=variable_manager,
# t = TaskInclude.load( loader=loader,
# task, )
# block=block, else:
# role=role,
# task_include=task_include,
# use_handlers=use_handlers,
# loader=loader
# )
# if cur_basedir and loader:
# loader.set_basedir(cur_basedir)
#else:
if True:
if use_handlers: if use_handlers:
t = Handler.load(task, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader) t = Handler.load(task, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
else: else:
@ -120,15 +110,3 @@ def load_list_of_roles(ds, current_role_path=None, variable_manager=None, loader
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

@ -24,7 +24,7 @@ from ansible.errors import AnsibleError, AnsibleParserError
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.become import Become from ansible.playbook.become import Become
from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles, compile_block_list from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles
from ansible.playbook.role import Role from ansible.playbook.role import Role
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable

@ -32,7 +32,7 @@ from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base from ansible.playbook.base import Base
from ansible.playbook.become import Become from ansible.playbook.become import Become
from ansible.playbook.conditional import Conditional from ansible.playbook.conditional import Conditional
from ansible.playbook.helpers import load_list_of_blocks, compile_block_list from ansible.playbook.helpers import load_list_of_blocks
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
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable

@ -78,7 +78,7 @@ class Task(Base, Conditional, Taggable, Become):
# FIXME: this should not be a Task # FIXME: this should not be a Task
_meta = FieldAttribute(isa='string') _meta = FieldAttribute(isa='string')
_name = FieldAttribute(isa='string') _name = FieldAttribute(isa='string', default='')
_no_log = FieldAttribute(isa='bool') _no_log = FieldAttribute(isa='bool')
_notify = FieldAttribute(isa='list') _notify = FieldAttribute(isa='list')
@ -167,7 +167,6 @@ class Task(Base, Conditional, Taggable, Become):
args_parser = ModuleArgsParser(task_ds=ds) args_parser = ModuleArgsParser(task_ds=ds)
(action, args, delegate_to) = args_parser.parse() (action, args, delegate_to) = args_parser.parse()
new_ds['action'] = action new_ds['action'] = action
new_ds['args'] = args new_ds['args'] = args
new_ds['delegate_to'] = delegate_to new_ds['delegate_to'] = delegate_to
@ -199,6 +198,8 @@ class Task(Base, Conditional, Taggable, Become):
def get_vars(self): def get_vars(self):
all_vars = self.vars.copy() all_vars = self.vars.copy()
if self._block:
all_vars.update(self._block.get_vars())
if self._task_include: if self._task_include:
all_vars.update(self._task_include.get_vars()) all_vars.update(self._task_include.get_vars())

@ -240,6 +240,9 @@ class PluginLoader:
continue continue
if path not in self._module_cache: if path not in self._module_cache:
self._module_cache[path] = imp.load_source('.'.join([self.package, name]), path) self._module_cache[path] = imp.load_source('.'.join([self.package, name]), path)
if kwargs.get('class_only', False):
yield getattr(self._module_cache[path], self.class_name)
else:
yield getattr(self._module_cache[path], self.class_name)(*args, **kwargs) yield getattr(self._module_cache[path], self.class_name)(*args, **kwargs)
action_loader = PluginLoader( action_loader = PluginLoader(

@ -231,7 +231,7 @@ class ActionModule(ActionBase):
self._remove_tempfile_if_content_defined(content, content_tempfile) self._remove_tempfile_if_content_defined(content, content_tempfile)
# fix file permissions when the copy is done as a different user # fix file permissions when the copy is done as a different user
if (self._connection_info.become and self._connection_info.become_user != 'root': if self._connection_info.become and self._connection_info.become_user != 'root':
self._remote_chmod('a+r', tmp_src, tmp) self._remote_chmod('a+r', tmp_src, tmp)
if raw: if raw:

@ -19,7 +19,7 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.utils.display import Display #from ansible.utils.display import Display
__all__ = ["CallbackBase"] __all__ = ["CallbackBase"]
@ -34,8 +34,8 @@ class CallbackBase:
# FIXME: the list of functions here needs to be updated once we have # FIXME: the list of functions here needs to be updated once we have
# finalized the list of callback methods used in the default callback # finalized the list of callback methods used in the default callback
def __init__(self): def __init__(self, display):
self._display = Display() self._display = display
def set_connection_info(self, conn_info): def set_connection_info(self, conn_info):
# FIXME: this is a temporary hack, as the connection info object # FIXME: this is a temporary hack, as the connection info object

@ -30,25 +30,15 @@ class CallbackModule(CallbackBase):
to stdout when new callback events are received. to stdout when new callback events are received.
''' '''
def _print_banner(self, msg, color=None): CALLBACK_VERSION = 2.0
'''
Prints a header-looking line with stars taking up to 80 columns def v2_on_any(self, *args, **kwargs):
of width (3 columns, minimum)
'''
msg = msg.strip()
star_len = (80 - len(msg))
if star_len < 0:
star_len = 3
stars = "*" * star_len
self._display.display("\n%s %s" % (msg, stars), color=color)
def on_any(self, *args, **kwargs):
pass pass
def runner_on_failed(self, task, result, ignore_errors=False): def v2_runner_on_failed(self, result, ignore_errors=False):
self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), json.dumps(result._result, ensure_ascii=False)), color='red') self._display.display("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), json.dumps(result._result, ensure_ascii=False)), color='red')
def runner_on_ok(self, task, result): def v2_runner_on_ok(self, result):
if result._task.action == 'include': if result._task.action == 'include':
msg = 'included: %s for %s' % (result._task.args.get('_raw_params'), result._host.name) msg = 'included: %s for %s' % (result._task.args.get('_raw_params'), result._host.name)
@ -68,7 +58,7 @@ class CallbackModule(CallbackBase):
msg += " => %s" % json.dumps(result._result, indent=indent, ensure_ascii=False) msg += " => %s" % json.dumps(result._result, indent=indent, ensure_ascii=False)
self._display.display(msg, color=color) self._display.display(msg, color=color)
def runner_on_skipped(self, task, result): def v2_runner_on_skipped(self, result):
msg = "skipping: [%s]" % result._host.get_name() msg = "skipping: [%s]" % result._host.get_name()
if self._display._verbosity > 0 or 'verbose_always' in result._result: if self._display._verbosity > 0 or 'verbose_always' in result._result:
indent = None indent = None
@ -78,57 +68,66 @@ class CallbackModule(CallbackBase):
msg += " => %s" % json.dumps(result._result, indent=indent, ensure_ascii=False) msg += " => %s" % json.dumps(result._result, indent=indent, ensure_ascii=False)
self._display.display(msg, color='cyan') self._display.display(msg, color='cyan')
def runner_on_unreachable(self, task, result): def v2_runner_on_unreachable(self, result):
self._display.display("fatal: [%s]: UNREACHABLE! => %s" % (result._host.get_name(), result._result), color='red') self._display.display("fatal: [%s]: UNREACHABLE! => %s" % (result._host.get_name(), result._result), color='red')
def runner_on_no_hosts(self, task): def v2_runner_on_no_hosts(self, task):
pass pass
def runner_on_async_poll(self, host, res, jid, clock): def v2_runner_on_async_poll(self, result):
pass pass
def runner_on_async_ok(self, host, res, jid): def v2_runner_on_async_ok(self, result):
pass pass
def runner_on_async_failed(self, host, res, jid): def v2_runner_on_async_failed(self, result):
pass pass
def playbook_on_start(self): def v2_runner_on_file_diff(self, result, diff):
pass pass
def playbook_on_notify(self, host, handler): def v2_playbook_on_start(self):
pass pass
def playbook_on_no_hosts_matched(self): def v2_playbook_on_notify(self, result, handler):
pass
def v2_playbook_on_no_hosts_matched(self):
self._display.display("skipping: no hosts matched", color='cyan') self._display.display("skipping: no hosts matched", color='cyan')
def playbook_on_no_hosts_remaining(self): def v2_playbook_on_no_hosts_remaining(self):
self._print_banner("NO MORE HOSTS LEFT") self._display.banner("NO MORE HOSTS LEFT")
def playbook_on_task_start(self, name, is_conditional): def v2_playbook_on_task_start(self, task, is_conditional):
self._print_banner("TASK [%s]" % name.strip()) self._display.banner("TASK [%s]" % task.get_name().strip())
def playbook_on_cleanup_task_start(self, name): def v2_playbook_on_cleanup_task_start(self, task):
self._print_banner("CLEANUP TASK [%s]" % name.strip()) self._display.banner("CLEANUP TASK [%s]" % task.get_name().strip())
def playbook_on_handler_task_start(self, name): def v2_playbook_on_handler_task_start(self, task):
self._print_banner("RUNNING HANDLER [%s]" % name.strip()) self._display.banner("RUNNING HANDLER [%s]" % task.get_name().strip())
def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None):
pass pass
def playbook_on_setup(self): def v2_playbook_on_setup(self):
pass pass
def playbook_on_import_for_host(self, host, imported_file): def v2_playbook_on_import_for_host(self, result, imported_file):
pass pass
def playbook_on_not_import_for_host(self, host, missing_file): def v2_playbook_on_not_import_for_host(self, result, missing_file):
pass pass
def playbook_on_play_start(self, name): def v2_playbook_on_play_start(self, play):
self._print_banner("PLAY [%s]" % name.strip()) name = play.get_name().strip()
if not name:
msg = "PLAY"
else:
msg = "PLAY [%s]" % name
self._display.banner(name)
def playbook_on_stats(self, stats): def v2_playbook_on_stats(self, stats):
pass pass

@ -31,6 +31,8 @@ class CallbackModule(CallbackBase):
to stdout when new callback events are received. to stdout when new callback events are received.
''' '''
CALLBACK_VERSION = 2.0
def _print_banner(self, msg): def _print_banner(self, msg):
''' '''
Prints a header-looking line with stars taking up to 80 columns Prints a header-looking line with stars taking up to 80 columns

@ -28,7 +28,7 @@ from ansible.inventory.host import Host
from ansible.inventory.group import Group from ansible.inventory.group import Group
from ansible.playbook.handler import Handler from ansible.playbook.handler import Handler
from ansible.playbook.helpers import load_list_of_blocks, compile_block_list from ansible.playbook.helpers import load_list_of_blocks
from ansible.playbook.role import ROLE_CACHE, hash_params from ansible.playbook.role import ROLE_CACHE, hash_params
from ansible.plugins import module_loader from ansible.plugins import module_loader
from ansible.utils.debug import debug from ansible.utils.debug import debug
@ -49,7 +49,7 @@ class StrategyBase:
self._inventory = tqm.get_inventory() self._inventory = tqm.get_inventory()
self._workers = tqm.get_workers() self._workers = tqm.get_workers()
self._notified_handlers = tqm.get_notified_handlers() self._notified_handlers = tqm.get_notified_handlers()
self._callback = tqm.get_callback() #self._callback = tqm.get_callback()
self._variable_manager = tqm.get_variable_manager() self._variable_manager = tqm.get_variable_manager()
self._loader = tqm.get_loader() self._loader = tqm.get_loader()
self._final_q = tqm._final_q self._final_q = tqm._final_q
@ -73,6 +73,9 @@ class StrategyBase:
debug("running handlers") debug("running handlers")
result &= self.run_handlers(iterator, connection_info) result &= self.run_handlers(iterator, connection_info)
# send the stats callback
self._tqm.send_callback('v2_playbook_on_stats', self._tqm._stats)
if not result: if not result:
if num_unreachable > 0: if num_unreachable > 0:
return 3 return 3
@ -84,7 +87,7 @@ class StrategyBase:
return 0 return 0
def get_hosts_remaining(self, play): def get_hosts_remaining(self, play):
return [host for host in self._inventory.get_hosts(play.hosts) if host.name not in self._tqm._failed_hosts and host.get_name() not in self._tqm._unreachable_hosts] return [host for host in self._inventory.get_hosts(play.hosts) if host.name not in self._tqm._failed_hosts and host.name not in self._tqm._unreachable_hosts]
def get_failed_hosts(self, play): def get_failed_hosts(self, play):
return [host for host in self._inventory.get_hosts(play.hosts) if host.name in self._tqm._failed_hosts] return [host for host in self._inventory.get_hosts(play.hosts) if host.name in self._tqm._failed_hosts]
@ -132,17 +135,23 @@ class StrategyBase:
task = task_result._task task = task_result._task
if result[0] == 'host_task_failed': if result[0] == 'host_task_failed':
if not task.ignore_errors: if not task.ignore_errors:
debug("marking %s as failed" % host.get_name()) debug("marking %s as failed" % host.name)
iterator.mark_host_failed(host) iterator.mark_host_failed(host)
self._tqm._failed_hosts[host.get_name()] = True self._tqm._failed_hosts[host.name] = True
self._callback.runner_on_failed(task, task_result) self._tqm._stats.increment('failures', host.name)
self._tqm.send_callback('v2_runner_on_failed', task_result)
elif result[0] == 'host_unreachable': elif result[0] == 'host_unreachable':
self._tqm._unreachable_hosts[host.get_name()] = True self._tqm._unreachable_hosts[host.name] = True
self._callback.runner_on_unreachable(task, task_result) self._tqm._stats.increment('dark', host.name)
self._tqm.send_callback('v2_runner_on_unreachable', task_result)
elif result[0] == 'host_task_skipped': elif result[0] == 'host_task_skipped':
self._callback.runner_on_skipped(task, task_result) self._tqm._stats.increment('skipped', host.name)
self._tqm.send_callback('v2_runner_on_skipped', task_result)
elif result[0] == 'host_task_ok': elif result[0] == 'host_task_ok':
self._callback.runner_on_ok(task, task_result) self._tqm._stats.increment('ok', host.name)
if 'changed' in task_result._result and task_result._result['changed']:
self._tqm._stats.increment('changed', host.name)
self._tqm.send_callback('v2_runner_on_ok', task_result)
self._pending_results -= 1 self._pending_results -= 1
if host.name in self._blocked_hosts: if host.name in self._blocked_hosts:
@ -160,22 +169,6 @@ class StrategyBase:
ret_results.append(task_result) ret_results.append(task_result)
#elif result[0] == 'include':
# host = result[1]
# task = result[2]
# include_file = result[3]
# include_vars = result[4]
#
# if isinstance(task, Handler):
# # FIXME: figure out how to make includes work for handlers
# pass
# else:
# original_task = iterator.get_original_task(host, task)
# if original_task and original_task._role:
# include_file = self._loader.path_dwim_relative(original_task._role._role_path, 'tasks', include_file)
# new_tasks = self._load_included_file(original_task, include_file, include_vars)
# iterator.add_tasks(host, new_tasks)
elif result[0] == 'add_host': elif result[0] == 'add_host':
task_result = result[1] task_result = result[1]
new_host_info = task_result.get('add_host', dict()) new_host_info = task_result.get('add_host', dict())
@ -322,14 +315,11 @@ class StrategyBase:
loader=self._loader loader=self._loader
) )
task_list = compile_block_list(block_list)
# set the vars for this task from those specified as params to the include # set the vars for this task from those specified as params to the include
for t in task_list: for b in block_list:
t.vars = included_file._args.copy() b._vars = included_file._args.copy()
return task_list return block_list
def cleanup(self, iterator, connection_info): def cleanup(self, iterator, connection_info):
''' '''
@ -361,7 +351,7 @@ class StrategyBase:
while work_to_do: while work_to_do:
work_to_do = False work_to_do = False
for host in failed_hosts: for host in failed_hosts:
host_name = host.get_name() host_name = host.name
if host_name in self._tqm._failed_hosts: if host_name in self._tqm._failed_hosts:
iterator.mark_host_failed(host) iterator.mark_host_failed(host)
@ -377,7 +367,7 @@ class StrategyBase:
self._blocked_hosts[host_name] = True self._blocked_hosts[host_name] = True
task = iterator.get_next_task_for_host(host) task = iterator.get_next_task_for_host(host)
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task) task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task)
self._callback.playbook_on_cleanup_task_start(task.get_name()) self._tqm.send_callback('v2_playbook_on_cleanup_task_start', task)
self._queue_task(host, task, task_vars, connection_info) self._queue_task(host, task, task_vars, connection_info)
self._process_pending_results(iterator) self._process_pending_results(iterator)
@ -398,31 +388,28 @@ class StrategyBase:
# FIXME: getting the handlers from the iterators play should be # FIXME: getting the handlers from the iterators play should be
# a method on the iterator, which may also filter the list # a method on the iterator, which may also filter the list
# of handlers based on the notified list # of handlers based on the notified list
handlers = compile_block_list(iterator._play.handlers)
for handler_block in iterator._play.handlers:
debug("handlers are: %s" % handlers) debug("handlers are: %s" % handlers)
for handler in handlers: # FIXME: handlers need to support the rescue/always portions of blocks too,
# but this may take some work in the iterator and gets tricky when
# we consider the ability of meta tasks to flush handlers
for handler in handler_block.block:
handler_name = handler.get_name() handler_name = handler.get_name()
if handler_name in self._notified_handlers and len(self._notified_handlers[handler_name]): if handler_name in self._notified_handlers and len(self._notified_handlers[handler_name]):
if not len(self.get_hosts_remaining(iterator._play)): if not len(self.get_hosts_remaining(iterator._play)):
self._callback.playbook_on_no_hosts_remaining() self._tqm.send_callback('v2_playbook_on_no_hosts_remaining')
result = False result = False
break break
self._tqm.send_callback('v2_playbook_on_handler_task_start', handler)
self._callback.playbook_on_handler_task_start(handler_name)
for host in self._notified_handlers[handler_name]: for host in self._notified_handlers[handler_name]:
if not handler.has_triggered(host): if not handler.has_triggered(host):
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=handler) task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=handler)
self._queue_task(host, handler, task_vars, connection_info) self._queue_task(host, handler, task_vars, connection_info)
handler.flag_for_host(host) handler.flag_for_host(host)
self._process_pending_results(iterator) self._process_pending_results(iterator)
self._wait_on_pending_results(iterator) self._wait_on_pending_results(iterator)
# wipe the notification list # wipe the notification list
self._notified_handlers[handler_name] = [] self._notified_handlers[handler_name] = []
debug("done running handlers, result is: %s" % result) debug("done running handlers, result is: %s" % result)
return result return result

@ -21,6 +21,7 @@ __metaclass__ = type
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.executor.play_iterator import PlayIterator from ansible.executor.play_iterator import PlayIterator
from ansible.playbook.block import Block
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.plugins import action_loader from ansible.plugins import action_loader
from ansible.plugins.strategies import StrategyBase from ansible.plugins.strategies import StrategyBase
@ -52,6 +53,9 @@ class StrategyModule(StrategyBase):
lowest_cur_block = len(iterator._blocks) lowest_cur_block = len(iterator._blocks)
for (k, v) in host_tasks.iteritems(): for (k, v) in host_tasks.iteritems():
if v is None:
continue
(s, t) = v (s, t) = v
if s.cur_block < lowest_cur_block and s.run_state != PlayIterator.ITERATING_COMPLETE: if s.cur_block < lowest_cur_block and s.run_state != PlayIterator.ITERATING_COMPLETE:
lowest_cur_block = s.cur_block lowest_cur_block = s.cur_block
@ -131,7 +135,7 @@ class StrategyModule(StrategyBase):
debug("done getting the remaining hosts for this loop") debug("done getting the remaining hosts for this loop")
if len(hosts_left) == 0: if len(hosts_left) == 0:
debug("out of hosts to run on") debug("out of hosts to run on")
self._callback.playbook_on_no_hosts_remaining() self._tqm.send_callback('v2_playbook_on_no_hosts_remaining')
result = False result = False
break break
@ -184,7 +188,6 @@ class StrategyModule(StrategyBase):
meta_action = task.args.get('_raw_params') meta_action = task.args.get('_raw_params')
if meta_action == 'noop': if meta_action == 'noop':
# FIXME: issue a callback for the noop here? # FIXME: issue a callback for the noop here?
print("%s => NOOP" % host)
continue continue
elif meta_action == 'flush_handlers': elif meta_action == 'flush_handlers':
self.run_handlers(iterator, connection_info) self.run_handlers(iterator, connection_info)
@ -192,7 +195,7 @@ class StrategyModule(StrategyBase):
raise AnsibleError("invalid meta action requested: %s" % meta_action, obj=task._ds) raise AnsibleError("invalid meta action requested: %s" % meta_action, obj=task._ds)
else: else:
if not callback_sent: if not callback_sent:
self._callback.playbook_on_task_start(task.get_name(), False) self._tqm.send_callback('v2_playbook_on_task_start', task, is_conditional=False)
callback_sent = True callback_sent = True
self._blocked_hosts[host.get_name()] = True self._blocked_hosts[host.get_name()] = True
@ -234,6 +237,10 @@ class StrategyModule(StrategyBase):
include_results = [ res._result ] include_results = [ res._result ]
for include_result in include_results: for include_result in include_results:
# if the task result was skipped or failed, continue
if 'skipped' in include_result and include_result['skipped'] or 'failed' in include_result:
continue
original_task = iterator.get_original_task(res._host, res._task) original_task = iterator.get_original_task(res._host, res._task)
if original_task and original_task._role: if original_task and original_task._role:
include_file = self._loader.path_dwim_relative(original_task._role._role_path, 'tasks', include_result['include']) include_file = self._loader.path_dwim_relative(original_task._role._role_path, 'tasks', include_result['include'])
@ -263,27 +270,31 @@ class StrategyModule(StrategyBase):
noop_task.args['_raw_params'] = 'noop' noop_task.args['_raw_params'] = 'noop'
noop_task.set_loader(iterator._play._loader) noop_task.set_loader(iterator._play._loader)
all_tasks = dict((host, []) for host in hosts_left) all_blocks = dict((host, []) for host in hosts_left)
for included_file in included_files: for included_file in included_files:
# included hosts get the task list while those excluded get an equal-length # included hosts get the task list while those excluded get an equal-length
# list of noop tasks, to make sure that they continue running in lock-step # list of noop tasks, to make sure that they continue running in lock-step
try: try:
new_tasks = self._load_included_file(included_file) new_blocks = self._load_included_file(included_file)
except AnsibleError, e: except AnsibleError, e:
for host in included_file._hosts: for host in included_file._hosts:
iterator.mark_host_failed(host) iterator.mark_host_failed(host)
# FIXME: callback here? # FIXME: callback here?
print(e) print(e)
noop_tasks = [noop_task for t in new_tasks] for new_block in new_blocks:
noop_block = Block(parent_block=task._block)
noop_block.block = [noop_task for t in new_block.block]
noop_block.always = [noop_task for t in new_block.always]
noop_block.rescue = [noop_task for t in new_block.rescue]
for host in hosts_left: for host in hosts_left:
if host in included_file._hosts: if host in included_file._hosts:
all_tasks[host].extend(new_tasks) all_blocks[host].append(new_block)
else: else:
all_tasks[host].extend(noop_tasks) all_blocks[host].append(noop_block)
for host in hosts_left: for host in hosts_left:
iterator.add_tasks(host, all_tasks[host]) iterator.add_tasks(host, all_blocks[host])
debug("results queue empty") debug("results queue empty")
except (IOError, EOFError), e: except (IOError, EOFError), e:

@ -68,6 +68,8 @@ def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False,
default=None) default=None)
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append", parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
help="set additional variables as key=value or YAML/JSON", default=[]) help="set additional variables as key=value or YAML/JSON", default=[])
parser.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user',
help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER)
if subset_opts: if subset_opts:
parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset', parser.add_option('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset',

@ -73,3 +73,20 @@ def stringc(text, color):
# --- end "pretty" # --- end "pretty"
def colorize(lead, num, color):
""" Print 'lead' = 'num' in 'color' """
if num != 0 and ANSIBLE_COLOR and color is not None:
return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color))
else:
return "%s=%-4s" % (lead, str(num))
def hostcolor(host, stats, color=True):
if ANSIBLE_COLOR and color:
if stats['failures'] != 0 or stats['unreachable'] != 0:
return "%-37s" % stringc(host, 'red')
elif stats['changed'] != 0:
return "%-37s" % stringc(host, 'yellow')
else:
return "%-37s" % stringc(host, 'green')
return "%-26s" % host

@ -112,3 +112,15 @@ class Display:
if C.SYSTEM_WARNINGS: if C.SYSTEM_WARNINGS:
self._warning(msg) self._warning(msg)
def banner(self, msg, color=None):
'''
Prints a header-looking line with stars taking up to 80 columns
of width (3 columns, minimum)
'''
msg = msg.strip()
star_len = (80 - len(msg))
if star_len < 0:
star_len = 3
stars = "*" * star_len
self.display("\n%s %s" % (msg, stars), color=color)

@ -162,10 +162,9 @@ class VariableManager:
all_vars = self._combine_vars(all_vars, self._group_vars_files['all']) all_vars = self._combine_vars(all_vars, self._group_vars_files['all'])
for group in host.get_groups(): for group in host.get_groups():
group_name = group.get_name()
all_vars = self._combine_vars(all_vars, group.get_vars()) all_vars = self._combine_vars(all_vars, group.get_vars())
if group_name in self._group_vars_files and group_name != 'all': if group.name in self._group_vars_files and group.name != 'all':
all_vars = self._combine_vars(all_vars, self._group_vars_files[group_name]) all_vars = self._combine_vars(all_vars, self._group_vars_files[group.name])
host_name = host.get_name() host_name = host.get_name()
if host_name in self._host_vars_files: if host_name in self._host_vars_files:
@ -228,7 +227,7 @@ class VariableManager:
''' '''
(name, ext) = os.path.splitext(os.path.basename(path)) (name, ext) = os.path.splitext(os.path.basename(path))
if ext not in ('yml', 'yaml'): if ext not in ('.yml', '.yaml'):
return os.path.basename(path) return os.path.basename(path)
else: else:
return name return name
@ -239,11 +238,11 @@ class VariableManager:
basename of the file without the extension basename of the file without the extension
''' '''
if os.path.isdir(path): if loader.is_directory(path):
data = dict() data = dict()
try: try:
names = os.listdir(path) names = loader.list_directory(path)
except os.error, err: except os.error, err:
raise AnsibleError("This folder cannot be listed: %s: %s." % (path, err.strerror)) raise AnsibleError("This folder cannot be listed: %s: %s." % (path, err.strerror))
@ -270,7 +269,7 @@ class VariableManager:
the extension, for matching against a given inventory host name the extension, for matching against a given inventory host name
''' '''
if os.path.exists(path): if loader.path_exists(path):
(name, data) = self._load_inventory_file(path, loader) (name, data) = self._load_inventory_file(path, loader)
self._host_vars_files[name] = data self._host_vars_files[name] = data
@ -281,7 +280,7 @@ class VariableManager:
the extension, for matching against a given inventory host name the extension, for matching against a given inventory host name
''' '''
if os.path.exists(path): if loader.path_exists(path):
(name, data) = self._load_inventory_file(path, loader) (name, data) = self._load_inventory_file(path, loader)
self._group_vars_files[name] = data self._group_vars_files[name] = data

@ -1,4 +1,4 @@
- debug: msg="this is the include, a=={{a}}" - debug: msg="this is the include, a=={{a}}"
- debug: msg="this is the second debug in the include" #- debug: msg="this is the second debug in the include"
- debug: msg="this is the third debug in the include, and a is still {{a}}" #- debug: msg="this is the third debug in the include, and a is still {{a}}"

@ -0,0 +1,3 @@
- debug: msg="this is the localhost include"
- include: common_include.yml

@ -6,3 +6,8 @@
- block: - block:
- block: - block:
- debug: msg="are we there yet?" - debug: msg="are we there yet?"
always:
- debug: msg="a random always block"
- fail:
rescue:
- debug: msg="rescuing from the fail"

@ -19,7 +19,7 @@
always: always:
- include: include.yml a=always - include: include.yml a=always
handlers: #handlers:
#- name: foo #- name: foo
# include: include.yml a="this is a handler" # include: include.yml a="this is a handler"

@ -47,6 +47,9 @@ class DictDataLoader(DataLoader):
def is_directory(self, path): def is_directory(self, path):
return path in self._known_directories return path in self._known_directories
def list_directory(self, path):
return [x for x in self._known_directories]
def _add_known_directory(self, directory): def _add_known_directory(self, directory):
if directory not in self._known_directories: if directory not in self._known_directories:
self._known_directories.append(directory) self._known_directories.append(directory)

@ -75,9 +75,3 @@ 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)

@ -24,6 +24,7 @@ from ansible.compat.tests.mock import patch, MagicMock
from ansible.errors import AnsibleError, AnsibleParserError from ansible.errors import AnsibleError, AnsibleParserError
from ansible.playbook import Playbook from ansible.playbook import Playbook
from ansible.vars import VariableManager
from test.mock.loader import DictDataLoader from test.mock.loader import DictDataLoader
@ -36,7 +37,8 @@ class TestPlaybook(unittest.TestCase):
pass pass
def test_empty_playbook(self): def test_empty_playbook(self):
p = Playbook() fake_loader = DictDataLoader({})
p = Playbook(loader=fake_loader)
def test_basic_playbook(self): def test_basic_playbook(self):
fake_loader = DictDataLoader({ fake_loader = DictDataLoader({
@ -61,6 +63,7 @@ class TestPlaybook(unittest.TestCase):
""", """,
}) })
self.assertRaises(AnsibleParserError, Playbook.load, "bad_list.yml", fake_loader) vm = VariableManager()
self.assertRaises(AnsibleParserError, Playbook.load, "bad_entry.yml", fake_loader) self.assertRaises(AnsibleParserError, Playbook.load, "bad_list.yml", vm, fake_loader)
self.assertRaises(AnsibleParserError, Playbook.load, "bad_entry.yml", vm, fake_loader)

@ -1,64 +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
from ansible.compat.tests import unittest
from ansible.errors import AnsibleParserError
from ansible.parsing.yaml.objects import AnsibleMapping
from ansible.playbook.task_include import TaskInclude
from test.mock.loader import DictDataLoader
class TestTaskInclude(unittest.TestCase):
def setUp(self):
self._fake_loader = DictDataLoader({
"foo.yml": """
- shell: echo "hello world"
"""
})
pass
def tearDown(self):
pass
def test_empty_task_include(self):
ti = TaskInclude()
def test_basic_task_include(self):
ti = TaskInclude.load(AnsibleMapping(include='foo.yml'), loader=self._fake_loader)
tasks = ti.compile()
def test_task_include_with_loop(self):
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', with_items=['a', 'b', 'c']), loader=self._fake_loader)
def test_task_include_with_conditional(self):
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', when="1 == 1"), loader=self._fake_loader)
def test_task_include_with_tags(self):
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', tags="foo"), loader=self._fake_loader)
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', tags=["foo", "bar"]), loader=self._fake_loader)
def test_task_include_errors(self):
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include=''), loader=self._fake_loader)
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include='foo.yml', vars="1"), loader=self._fake_loader)
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include='foo.yml a=1', vars=dict(b=2)), loader=self._fake_loader)

@ -35,8 +35,10 @@ class TestVariableManager(unittest.TestCase):
pass pass
def test_basic_manager(self): def test_basic_manager(self):
fake_loader = DictDataLoader({})
v = VariableManager() v = VariableManager()
self.assertEqual(v.get_vars(), dict()) self.assertEqual(v.get_vars(loader=fake_loader), dict())
self.assertEqual( self.assertEqual(
v._merge_dicts( v._merge_dicts(
@ -52,23 +54,26 @@ class TestVariableManager(unittest.TestCase):
) )
def test_manager_extra_vars(self): def test_variable_manager_extra_vars(self):
fake_loader = DictDataLoader({})
extra_vars = dict(a=1, b=2, c=3) extra_vars = dict(a=1, b=2, c=3)
v = VariableManager() v = VariableManager()
v.set_extra_vars(extra_vars) v.set_extra_vars(extra_vars)
self.assertEqual(v.get_vars(), extra_vars) for (key, val) in extra_vars.iteritems():
self.assertIsNot(v.extra_vars, extra_vars) self.assertEqual(v.get_vars(loader=fake_loader).get(key), val)
self.assertIsNot(v.extra_vars.get(key), val)
def test_manager_host_vars_file(self): def test_variable_manager_host_vars_file(self):
fake_loader = DictDataLoader({ fake_loader = DictDataLoader({
"host_vars/hostname1.yml": """ "host_vars/hostname1.yml": """
foo: bar foo: bar
""" """
}) })
v = VariableManager(loader=fake_loader) v = VariableManager()
v.add_host_vars_file("host_vars/hostname1.yml") v.add_host_vars_file("host_vars/hostname1.yml", loader=fake_loader)
self.assertIn("hostname1", v._host_vars_files) self.assertIn("hostname1", v._host_vars_files)
self.assertEqual(v._host_vars_files["hostname1"], dict(foo="bar")) self.assertEqual(v._host_vars_files["hostname1"], dict(foo="bar"))
@ -77,37 +82,43 @@ class TestVariableManager(unittest.TestCase):
mock_host.get_vars.return_value = dict() mock_host.get_vars.return_value = dict()
mock_host.get_groups.return_value = () mock_host.get_groups.return_value = ()
self.assertEqual(v.get_vars(host=mock_host), dict(foo="bar")) self.assertEqual(v.get_vars(loader=fake_loader, host=mock_host).get("foo"), "bar")
def test_manager_group_vars_file(self): def test_variable_manager_group_vars_file(self):
fake_loader = DictDataLoader({ fake_loader = DictDataLoader({
"group_vars/somegroup.yml": """ "group_vars/somegroup.yml": """
foo: bar foo: bar
""" """
}) })
v = VariableManager(loader=fake_loader) v = VariableManager()
v.add_group_vars_file("group_vars/somegroup.yml") v.add_group_vars_file("group_vars/somegroup.yml", loader=fake_loader)
self.assertIn("somegroup", v._group_vars_files) self.assertIn("somegroup", v._group_vars_files)
self.assertEqual(v._group_vars_files["somegroup"], dict(foo="bar")) self.assertEqual(v._group_vars_files["somegroup"], dict(foo="bar"))
mock_group = MagicMock()
mock_group.name.return_value = "somegroup"
mock_group.get_ancestors.return_value = ()
mock_host = MagicMock() mock_host = MagicMock()
mock_host.get_name.return_value = "hostname1" mock_host.get_name.return_value = "hostname1"
mock_host.get_vars.return_value = dict() mock_host.get_vars.return_value = dict()
mock_host.get_groups.return_value = ["somegroup"] mock_host.get_groups.return_value = (mock_group)
self.assertEqual(v.get_vars(loader=fake_loader, host=mock_host).get("foo"), "bar")
self.assertEqual(v.get_vars(host=mock_host), dict(foo="bar")) def test_variable_manager_play_vars(self):
fake_loader = DictDataLoader({})
def test_manager_play_vars(self):
mock_play = MagicMock() mock_play = MagicMock()
mock_play.get_vars.return_value = dict(foo="bar") mock_play.get_vars.return_value = dict(foo="bar")
mock_play.get_roles.return_value = [] mock_play.get_roles.return_value = []
mock_play.get_vars_files.return_value = [] mock_play.get_vars_files.return_value = []
v = VariableManager() v = VariableManager()
self.assertEqual(v.get_vars(play=mock_play), dict(foo="bar")) self.assertEqual(v.get_vars(loader=fake_loader, play=mock_play).get("foo"), "bar")
def test_manager_play_vars_files(self): def test_variable_manager_play_vars_files(self):
fake_loader = DictDataLoader({ fake_loader = DictDataLoader({
"/path/to/somefile.yml": """ "/path/to/somefile.yml": """
foo: bar foo: bar
@ -119,13 +130,15 @@ class TestVariableManager(unittest.TestCase):
mock_play.get_roles.return_value = [] mock_play.get_roles.return_value = []
mock_play.get_vars_files.return_value = ['/path/to/somefile.yml'] mock_play.get_vars_files.return_value = ['/path/to/somefile.yml']
v = VariableManager(loader=fake_loader) v = VariableManager()
self.assertEqual(v.get_vars(play=mock_play), dict(foo="bar")) self.assertEqual(v.get_vars(loader=fake_loader, play=mock_play).get("foo"), "bar")
def test_variable_manager_task_vars(self):
fake_loader = DictDataLoader({})
def test_manager_task_vars(self):
mock_task = MagicMock() mock_task = MagicMock()
mock_task.get_vars.return_value = dict(foo="bar") mock_task.get_vars.return_value = dict(foo="bar")
v = VariableManager() v = VariableManager()
self.assertEqual(v.get_vars(task=mock_task), dict(foo="bar")) self.assertEqual(v.get_vars(loader=fake_loader, task=mock_task).get("foo"), "bar")

Loading…
Cancel
Save