Fixes #4777 add --force-handlers option to run handlers even if tasks fail

pull/6593/merge
James Tanner 11 years ago
parent 7a53862046
commit 9169a11088

@ -78,6 +78,8 @@ def main(args):
help="one-step-at-a-time: confirm each task before running") help="one-step-at-a-time: confirm each task before running")
parser.add_option('--start-at-task', dest='start_at', parser.add_option('--start-at-task', dest='start_at',
help="start the playbook at the task matching this name") help="start the playbook at the task matching this name")
parser.add_option('--force-handlers', dest='force_handlers', action='store_true',
help="run handlers even if a task fails")
options, args = parser.parse_args(args) options, args = parser.parse_args(args)
@ -191,7 +193,8 @@ def main(args):
su=options.su, su=options.su,
su_pass=su_pass, su_pass=su_pass,
su_user=options.su_user, su_user=options.su_user,
vault_password=vault_pass vault_password=vault_pass,
force_handlers=options.force_handlers
) )
if options.listhosts or options.listtasks or options.syntax: if options.listhosts or options.listtasks or options.syntax:

@ -73,6 +73,7 @@ class PlayBook(object):
su_user = False, su_user = False,
su_pass = False, su_pass = False,
vault_password = False, vault_password = False,
force_handlers = False,
): ):
""" """
@ -92,6 +93,8 @@ class PlayBook(object):
sudo: if not specified per play, requests all plays use sudo mode sudo: if not specified per play, requests all plays use sudo mode
inventory: can be specified instead of host_list to use a pre-existing inventory object inventory: can be specified instead of host_list to use a pre-existing inventory object
check: don't change anything, just try to detect some potential changes check: don't change anything, just try to detect some potential changes
any_errors_fatal: terminate the entire execution immediately when one of the hosts has failed
force_handlers: continue to notify and run handlers even if a task fails
""" """
self.SETUP_CACHE = SETUP_CACHE self.SETUP_CACHE = SETUP_CACHE
@ -140,6 +143,7 @@ class PlayBook(object):
self.su_user = su_user self.su_user = su_user
self.su_pass = su_pass self.su_pass = su_pass
self.vault_password = vault_password self.vault_password = vault_password
self.force_handlers = force_handlers
self.callbacks.playbook = self self.callbacks.playbook = self
self.runner_callbacks.playbook = self self.runner_callbacks.playbook = self
@ -568,7 +572,7 @@ class PlayBook(object):
def _run_play(self, play): def _run_play(self, play):
''' run a list of tasks for a given pattern, in order ''' ''' run a list of tasks for a given pattern, in order '''
self.callbacks.on_play_start(play.name) self.callbacks.on_play_start(play.name)
# Get the hosts for this play # Get the hosts for this play
play._play_hosts = self.inventory.list_hosts(play.hosts) play._play_hosts = self.inventory.list_hosts(play.hosts)
@ -606,42 +610,9 @@ class PlayBook(object):
for task in play.tasks(): for task in play.tasks():
# skip handlers until play is finished
if task.meta is not None: if task.meta is not None:
continue
# meta tasks are an internalism and are not valid for end-user playbook usage
# here a meta task is a placeholder that signals handlers should be run
if task.meta == 'flush_handlers':
fired_names = {}
for handler in play.handlers():
if len(handler.notified_by) > 0:
self.inventory.restrict_to(handler.notified_by)
# Resolve the variables first
handler_name = template(play.basedir, handler.name, handler.module_vars)
if handler_name not in fired_names:
self._run_task(play, handler, True)
# prevent duplicate handler includes from running more than once
fired_names[handler_name] = 1
host_list = self._trim_unavailable_hosts(play._play_hosts)
if handler.any_errors_fatal and len(host_list) < hosts_count:
play.max_fail_pct = 0
if (hosts_count - len(host_list)) > int((play.max_fail_pct)/100.0 * hosts_count):
host_list = None
if not host_list:
self.callbacks.on_no_hosts_remaining()
return False
self.inventory.lift_restriction()
new_list = handler.notified_by[:]
for host in handler.notified_by:
if host in on_hosts:
while host in new_list:
new_list.remove(host)
handler.notified_by = new_list
continue
# only run the task if the requested tags match # only run the task if the requested tags match
should_run = False should_run = False
@ -685,10 +656,58 @@ class PlayBook(object):
# if no hosts remain, drop out # if no hosts remain, drop out
if not host_list: if not host_list:
self.callbacks.on_no_hosts_remaining() if self.force_handlers:
if not self.run_handlers(play):
return False
else:
self.callbacks.on_no_hosts_remaining()
return False return False
else:
self.inventory.lift_also_restriction() self.inventory.lift_also_restriction()
if not self.run_handlers(play):
return False
return True return True
def run_handlers(self, play):
on_hosts = play._play_hosts
hosts_count = len(on_hosts)
for task in play.tasks():
if task.meta is not None:
# meta tasks are an internalism and are not valid for end-user playbook usage
# here a meta task is a placeholder that signals handlers should be run
if task.meta == 'flush_handlers':
fired_names = {}
for handler in play.handlers():
if len(handler.notified_by) > 0:
self.inventory.restrict_to(handler.notified_by)
# Resolve the variables first
handler_name = template(play.basedir, handler.name, handler.module_vars)
if handler_name not in fired_names:
self._run_task(play, handler, True)
# prevent duplicate handler includes from running more than once
fired_names[handler_name] = 1
host_list = self._trim_unavailable_hosts(play._play_hosts)
if handler.any_errors_fatal and len(host_list) < hosts_count:
play.max_fail_pct = 0
if (hosts_count - len(host_list)) > int((play.max_fail_pct)/100.0 * hosts_count):
host_list = None
if not host_list and not self.force_handlers:
self.callbacks.on_no_hosts_remaining()
return False
self.inventory.lift_restriction()
new_list = handler.notified_by[:]
for host in handler.notified_by:
if host in on_hosts:
while host in new_list:
new_list.remove(host)
handler.notified_by = new_list
continue
return True

Loading…
Cancel
Save