2014-01-04 19:31:44 +01:00
|
|
|
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
2012-02-24 05:26:16 +01:00
|
|
|
#
|
2012-02-29 01:08:09 +01:00
|
|
|
# This file is part of Ansible
|
2012-02-24 05:26:16 +01:00
|
|
|
#
|
2012-02-29 01:08:09 +01:00
|
|
|
# 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/>.
|
2012-02-24 05:26:16 +01:00
|
|
|
|
2012-04-13 14:39:54 +02:00
|
|
|
import ansible.inventory
|
2012-02-24 05:26:16 +01:00
|
|
|
import ansible.constants as C
|
2013-04-20 15:09:35 +02:00
|
|
|
import ansible.runner
|
2013-04-25 03:59:47 +02:00
|
|
|
from ansible.utils.template import template
|
2012-03-18 22:16:12 +01:00
|
|
|
from ansible import utils
|
|
|
|
from ansible import errors
|
2012-09-15 02:02:38 +02:00
|
|
|
import ansible.callbacks
|
2012-03-03 16:53:15 +01:00
|
|
|
import os
|
2012-11-09 13:09:20 +01:00
|
|
|
import shlex
|
2012-07-15 16:57:22 +02:00
|
|
|
import collections
|
2012-05-26 06:37:34 +02:00
|
|
|
from play import Play
|
2013-04-08 05:37:10 +02:00
|
|
|
import StringIO
|
|
|
|
import pipes
|
2012-03-06 02:09:03 +01:00
|
|
|
|
2012-07-26 02:45:51 +02:00
|
|
|
SETUP_CACHE = collections.defaultdict(dict)
|
|
|
|
|
2012-02-24 05:26:16 +01:00
|
|
|
class PlayBook(object):
|
2012-03-03 04:03:03 +01:00
|
|
|
'''
|
2012-08-07 02:07:02 +02:00
|
|
|
runs an ansible playbook, given as a datastructure or YAML filename.
|
|
|
|
A playbook is a deployment, config management, or automation based
|
2012-07-15 17:20:59 +02:00
|
|
|
set of commands to run in series.
|
|
|
|
|
2012-08-07 02:07:02 +02:00
|
|
|
multiple plays/tasks do not execute simultaneously, but tasks in each
|
|
|
|
pattern do execute in parallel (according to the number of forks
|
2012-07-15 17:20:59 +02:00
|
|
|
requested) among the hosts they address
|
2012-02-24 05:26:16 +01:00
|
|
|
'''
|
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-03-03 04:03:03 +01:00
|
|
|
def __init__(self,
|
2012-03-26 01:05:27 +02:00
|
|
|
playbook = None,
|
|
|
|
host_list = C.DEFAULT_HOST_LIST,
|
2012-11-18 18:37:30 +01:00
|
|
|
module_path = None,
|
2012-03-26 01:05:27 +02:00
|
|
|
forks = C.DEFAULT_FORKS,
|
|
|
|
timeout = C.DEFAULT_TIMEOUT,
|
|
|
|
remote_user = C.DEFAULT_REMOTE_USER,
|
|
|
|
remote_pass = C.DEFAULT_REMOTE_PASS,
|
2012-04-14 01:06:11 +02:00
|
|
|
sudo_pass = C.DEFAULT_SUDO_PASS,
|
2012-10-18 04:50:17 +02:00
|
|
|
remote_port = None,
|
2012-04-11 18:39:04 +02:00
|
|
|
transport = C.DEFAULT_TRANSPORT,
|
2012-05-14 22:52:48 +02:00
|
|
|
private_key_file = C.DEFAULT_PRIVATE_KEY_FILE,
|
2012-03-26 01:05:27 +02:00
|
|
|
callbacks = None,
|
|
|
|
runner_callbacks = None,
|
2012-04-17 04:15:55 +02:00
|
|
|
stats = None,
|
2012-04-27 01:56:10 +02:00
|
|
|
sudo = False,
|
2012-05-07 17:37:50 +02:00
|
|
|
sudo_user = C.DEFAULT_SUDO_USER,
|
2012-07-12 01:51:26 +02:00
|
|
|
extra_vars = None,
|
2012-08-10 08:45:29 +02:00
|
|
|
only_tags = None,
|
2013-06-25 07:33:29 +02:00
|
|
|
skip_tags = None,
|
2012-11-27 14:42:58 +01:00
|
|
|
subset = C.DEFAULT_SUBSET,
|
2013-02-04 01:46:25 +01:00
|
|
|
inventory = None,
|
2013-02-08 04:51:33 +01:00
|
|
|
check = False,
|
2013-03-28 07:17:01 +01:00
|
|
|
diff = False,
|
2014-01-21 02:19:03 +01:00
|
|
|
any_errors_fatal = False,
|
|
|
|
su = False,
|
|
|
|
su_user = False,
|
|
|
|
su_pass = False,
|
2014-02-11 18:03:11 +01:00
|
|
|
vault_password = False,
|
2014-03-24 15:28:48 +01:00
|
|
|
force_handlers = False,
|
2014-01-21 02:19:03 +01:00
|
|
|
):
|
2012-03-26 01:05:27 +02:00
|
|
|
|
2012-04-17 05:45:15 +02:00
|
|
|
"""
|
|
|
|
playbook: path to a playbook file
|
|
|
|
host_list: path to a file like /etc/ansible/hosts
|
|
|
|
module_path: path to ansible modules, like /usr/share/ansible/
|
|
|
|
forks: desired level of paralellism
|
|
|
|
timeout: connection timeout
|
|
|
|
remote_user: run as this user if not specified in a particular play
|
|
|
|
remote_pass: use this remote password (for all plays) vs using SSH keys
|
|
|
|
sudo_pass: if sudo==True, and a password is required, this is the sudo password
|
|
|
|
remote_port: default remote port to use if not specified with the host or play
|
|
|
|
transport: how to connect to hosts that don't specify a transport (local, paramiko, etc)
|
|
|
|
callbacks output callbacks for the playbook
|
|
|
|
runner_callbacks: more callbacks, this time for the runner API
|
|
|
|
stats: holds aggregrate data about events occuring to each host
|
|
|
|
sudo: if not specified per play, requests all plays use sudo mode
|
2012-11-27 14:42:58 +01:00
|
|
|
inventory: can be specified instead of host_list to use a pre-existing inventory object
|
2013-02-04 01:46:25 +01:00
|
|
|
check: don't change anything, just try to detect some potential changes
|
2014-03-24 15:28:48 +01:00
|
|
|
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
|
2012-04-17 05:45:15 +02:00
|
|
|
"""
|
|
|
|
|
2012-07-26 02:45:51 +02:00
|
|
|
self.SETUP_CACHE = SETUP_CACHE
|
2012-06-01 04:08:00 +02:00
|
|
|
|
2014-01-17 10:30:57 +01:00
|
|
|
arguments = []
|
|
|
|
if playbook is None:
|
|
|
|
arguments.append('playbook')
|
|
|
|
if callbacks is None:
|
|
|
|
arguments.append('callbacks')
|
|
|
|
if runner_callbacks is None:
|
|
|
|
arguments.append('runner_callbacks')
|
|
|
|
if stats is None:
|
|
|
|
arguments.append('stats')
|
|
|
|
if arguments:
|
|
|
|
raise Exception('PlayBook missing required arguments: %s' % ', '.join(arguments))
|
2012-03-26 01:05:27 +02:00
|
|
|
|
2012-04-27 01:56:10 +02:00
|
|
|
if extra_vars is None:
|
|
|
|
extra_vars = {}
|
2012-07-12 01:51:26 +02:00
|
|
|
if only_tags is None:
|
|
|
|
only_tags = [ 'all' ]
|
2013-06-25 07:33:29 +02:00
|
|
|
if skip_tags is None:
|
|
|
|
skip_tags = []
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2013-02-04 01:46:25 +01:00
|
|
|
self.check = check
|
2013-02-08 04:51:33 +01:00
|
|
|
self.diff = diff
|
2012-03-26 01:05:27 +02:00
|
|
|
self.module_path = module_path
|
|
|
|
self.forks = forks
|
|
|
|
self.timeout = timeout
|
|
|
|
self.remote_user = remote_user
|
|
|
|
self.remote_pass = remote_pass
|
2012-03-28 23:05:31 +02:00
|
|
|
self.remote_port = remote_port
|
2012-04-11 18:39:04 +02:00
|
|
|
self.transport = transport
|
2012-03-26 01:05:27 +02:00
|
|
|
self.callbacks = callbacks
|
|
|
|
self.runner_callbacks = runner_callbacks
|
|
|
|
self.stats = stats
|
2012-04-17 04:15:55 +02:00
|
|
|
self.sudo = sudo
|
2012-04-14 01:06:11 +02:00
|
|
|
self.sudo_pass = sudo_pass
|
2012-05-07 00:24:04 +02:00
|
|
|
self.sudo_user = sudo_user
|
2012-04-27 01:56:10 +02:00
|
|
|
self.extra_vars = extra_vars
|
2012-05-03 00:29:46 +02:00
|
|
|
self.global_vars = {}
|
2012-05-14 22:52:48 +02:00
|
|
|
self.private_key_file = private_key_file
|
2012-07-12 01:51:26 +02:00
|
|
|
self.only_tags = only_tags
|
2013-06-25 07:33:29 +02:00
|
|
|
self.skip_tags = skip_tags
|
2013-03-28 07:17:01 +01:00
|
|
|
self.any_errors_fatal = any_errors_fatal
|
2014-01-21 02:19:03 +01:00
|
|
|
self.su = su
|
|
|
|
self.su_user = su_user
|
|
|
|
self.su_pass = su_pass
|
2014-02-11 18:03:11 +01:00
|
|
|
self.vault_password = vault_password
|
2014-03-24 15:28:48 +01:00
|
|
|
self.force_handlers = force_handlers
|
2012-03-03 16:53:15 +01:00
|
|
|
|
2013-03-26 03:47:06 +01:00
|
|
|
self.callbacks.playbook = self
|
|
|
|
self.runner_callbacks.playbook = self
|
|
|
|
|
2012-11-27 14:42:58 +01:00
|
|
|
if inventory is None:
|
|
|
|
self.inventory = ansible.inventory.Inventory(host_list)
|
|
|
|
self.inventory.subset(subset)
|
|
|
|
else:
|
|
|
|
self.inventory = inventory
|
2012-10-13 02:07:05 +02:00
|
|
|
|
2013-07-28 18:47:26 +02:00
|
|
|
if self.module_path is not None:
|
2013-08-03 20:54:37 +02:00
|
|
|
utils.plugins.module_finder.add_directory(self.module_path)
|
2013-07-28 18:47:26 +02:00
|
|
|
|
2012-12-19 13:22:57 +01:00
|
|
|
self.basedir = os.path.dirname(playbook) or '.'
|
2013-06-05 23:25:24 +02:00
|
|
|
utils.plugins.push_basedir(self.basedir)
|
2013-07-18 22:45:18 +02:00
|
|
|
vars = extra_vars.copy()
|
2013-10-25 07:53:52 +02:00
|
|
|
vars['playbook_dir'] = self.basedir
|
2013-03-26 03:32:01 +01:00
|
|
|
if self.inventory.basedir() is not None:
|
|
|
|
vars['inventory_dir'] = self.inventory.basedir()
|
2013-08-10 12:59:17 +02:00
|
|
|
|
|
|
|
if self.inventory.src() is not None:
|
|
|
|
vars['inventory_file'] = self.inventory.src()
|
|
|
|
|
2013-04-08 05:37:10 +02:00
|
|
|
self.filename = playbook
|
2013-03-26 03:32:01 +01:00
|
|
|
(self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook, vars)
|
2013-06-05 23:25:24 +02:00
|
|
|
ansible.callbacks.load_callback_plugins()
|
2012-06-25 20:57:09 +02:00
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
2012-07-12 06:17:51 +02:00
|
|
|
|
2012-11-09 13:09:20 +01:00
|
|
|
def _load_playbook_from_file(self, path, vars={}):
|
2012-07-12 06:17:51 +02:00
|
|
|
'''
|
2012-07-15 17:20:59 +02:00
|
|
|
run top level error checking on playbooks and allow them to include other playbooks.
|
2012-07-12 06:17:51 +02:00
|
|
|
'''
|
|
|
|
|
2014-02-11 18:03:11 +01:00
|
|
|
playbook_data = utils.parse_yaml_from_file(path, vault_password=self.vault_password)
|
2012-07-12 06:17:51 +02:00
|
|
|
accumulated_plays = []
|
2012-09-10 04:43:27 +02:00
|
|
|
play_basedirs = []
|
2012-07-12 06:17:51 +02:00
|
|
|
|
|
|
|
if type(playbook_data) != list:
|
2014-01-03 19:46:31 +01:00
|
|
|
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list, got %s" % type(playbook_data))
|
2012-07-12 06:17:51 +02:00
|
|
|
|
2012-12-19 13:22:57 +01:00
|
|
|
basedir = os.path.dirname(path) or '.'
|
2012-11-18 18:37:30 +01:00
|
|
|
utils.plugins.push_basedir(basedir)
|
2012-07-12 06:17:51 +02:00
|
|
|
for play in playbook_data:
|
2012-07-15 15:32:47 +02:00
|
|
|
if type(play) != dict:
|
2013-05-13 13:22:57 +02:00
|
|
|
raise errors.AnsibleError("parse error: each play in a playbook must be a YAML dictionary (hash), recieved: %s" % play)
|
2013-04-10 02:13:55 +02:00
|
|
|
|
2012-07-15 15:32:47 +02:00
|
|
|
if 'include' in play:
|
2013-04-10 02:13:55 +02:00
|
|
|
# a playbook (list of plays) decided to include some other list of plays
|
|
|
|
# from another file. The result is a flat list of plays in the end.
|
|
|
|
|
2013-04-25 03:59:47 +02:00
|
|
|
tokens = shlex.split(play['include'])
|
2012-11-09 13:09:20 +01:00
|
|
|
|
2013-04-10 02:13:55 +02:00
|
|
|
incvars = vars.copy()
|
|
|
|
if 'vars' in play:
|
|
|
|
if isinstance(play['vars'], dict):
|
|
|
|
incvars.update(play['vars'])
|
|
|
|
elif isinstance(play['vars'], list):
|
|
|
|
for v in play['vars']:
|
|
|
|
incvars.update(v)
|
|
|
|
|
|
|
|
# allow key=value parameters to be specified on the include line
|
|
|
|
# to set variables
|
|
|
|
|
|
|
|
for t in tokens[1:]:
|
|
|
|
|
|
|
|
(k,v) = t.split("=", 1)
|
2013-04-23 04:03:39 +02:00
|
|
|
incvars[k] = template(basedir, v, incvars)
|
2013-04-10 21:04:41 +02:00
|
|
|
|
2013-04-23 04:03:39 +02:00
|
|
|
included_path = utils.path_dwim(basedir, template(basedir, tokens[0], incvars))
|
2013-04-10 21:04:41 +02:00
|
|
|
(plays, basedirs) = self._load_playbook_from_file(included_path, incvars)
|
|
|
|
for p in plays:
|
|
|
|
# support for parameterized play includes works by passing
|
|
|
|
# those variables along to the subservient play
|
|
|
|
if 'vars' not in p:
|
|
|
|
p['vars'] = {}
|
|
|
|
if isinstance(p['vars'], dict):
|
|
|
|
p['vars'].update(incvars)
|
|
|
|
elif isinstance(p['vars'], list):
|
|
|
|
# nobody should really do this, but handle vars: a=1 b=2
|
2014-01-29 19:29:26 +01:00
|
|
|
p['vars'].extend([{k:v} for k,v in incvars.iteritems()])
|
2013-04-10 21:04:41 +02:00
|
|
|
|
|
|
|
accumulated_plays.extend(plays)
|
|
|
|
play_basedirs.extend(basedirs)
|
|
|
|
|
2012-07-15 15:32:47 +02:00
|
|
|
else:
|
2013-04-10 02:13:55 +02:00
|
|
|
|
|
|
|
# this is a normal (non-included play)
|
2012-07-15 15:32:47 +02:00
|
|
|
accumulated_plays.append(play)
|
2012-11-09 13:09:20 +01:00
|
|
|
play_basedirs.append(basedir)
|
2012-07-12 06:17:51 +02:00
|
|
|
|
2012-09-10 04:43:27 +02:00
|
|
|
return (accumulated_plays, play_basedirs)
|
2012-07-12 06:17:51 +02:00
|
|
|
|
|
|
|
# *****************************************************
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2012-02-24 07:02:24 +01:00
|
|
|
def run(self):
|
2012-02-27 02:18:42 +01:00
|
|
|
''' run all patterns in the playbook '''
|
2012-08-26 05:10:43 +02:00
|
|
|
plays = []
|
|
|
|
matched_tags_all = set()
|
|
|
|
unmatched_tags_all = set()
|
2012-02-24 07:02:24 +01:00
|
|
|
|
2012-02-27 02:18:42 +01:00
|
|
|
# loop through all patterns and run them
|
2012-03-07 01:24:36 +01:00
|
|
|
self.callbacks.on_start()
|
2012-09-10 04:43:27 +02:00
|
|
|
for (play_ds, play_basedir) in zip(self.playbook, self.play_basedirs):
|
2014-03-07 04:11:57 +01:00
|
|
|
play = Play(self, play_ds, play_basedir, vault_password=self.vault_password)
|
2013-04-20 15:09:35 +02:00
|
|
|
assert play is not None
|
2013-06-25 07:33:29 +02:00
|
|
|
|
2012-08-26 05:10:43 +02:00
|
|
|
matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
|
|
|
|
matched_tags_all = matched_tags_all | matched_tags
|
|
|
|
unmatched_tags_all = unmatched_tags_all | unmatched_tags
|
|
|
|
|
2013-06-25 07:33:29 +02:00
|
|
|
# Remove tasks we wish to skip
|
|
|
|
matched_tags = matched_tags - set(self.skip_tags)
|
|
|
|
|
2012-08-26 05:10:43 +02:00
|
|
|
# if we have matched_tags, the play must be run.
|
|
|
|
# if the play contains no tasks, assume we just want to gather facts
|
2013-04-25 04:28:06 +02:00
|
|
|
# in this case there are actually 3 meta tasks (handler flushes) not 0
|
|
|
|
# tasks, so that's why there's a check against 3
|
|
|
|
if (len(matched_tags) > 0 or len(play.tasks()) == 3):
|
2012-08-26 05:10:43 +02:00
|
|
|
plays.append(play)
|
|
|
|
|
2013-06-25 07:33:29 +02:00
|
|
|
# if the playbook is invoked with --tags or --skip-tags that don't
|
|
|
|
# exist at all in the playbooks then we need to raise an error so that
|
|
|
|
# the user can correct the arguments.
|
|
|
|
unknown_tags = ((set(self.only_tags) | set(self.skip_tags)) -
|
|
|
|
(matched_tags_all | unmatched_tags_all))
|
2012-09-10 04:43:27 +02:00
|
|
|
unknown_tags.discard('all')
|
|
|
|
|
2012-08-26 05:10:43 +02:00
|
|
|
if len(unknown_tags) > 0:
|
|
|
|
unmatched_tags_all.discard('all')
|
2012-08-28 03:31:20 +02:00
|
|
|
msg = 'tag(s) not found in playbook: %s. possible values: %s'
|
|
|
|
unknown = ','.join(sorted(unknown_tags))
|
|
|
|
unmatched = ','.join(sorted(unmatched_tags_all))
|
|
|
|
raise errors.AnsibleError(msg % (unknown, unmatched))
|
2012-08-26 05:10:43 +02:00
|
|
|
|
|
|
|
for play in plays:
|
2013-06-09 18:17:57 +02:00
|
|
|
ansible.callbacks.set_play(self.callbacks, play)
|
|
|
|
ansible.callbacks.set_play(self.runner_callbacks, play)
|
2012-10-01 03:06:00 +02:00
|
|
|
if not self._run_play(play):
|
|
|
|
break
|
2013-06-09 18:17:57 +02:00
|
|
|
ansible.callbacks.set_play(self.callbacks, None)
|
|
|
|
ansible.callbacks.set_play(self.runner_callbacks, None)
|
2012-08-26 05:10:43 +02:00
|
|
|
|
2012-02-27 02:18:42 +01:00
|
|
|
# summarize the results
|
2012-02-25 21:21:11 +01:00
|
|
|
results = {}
|
2012-03-26 01:05:27 +02:00
|
|
|
for host in self.stats.processed.keys():
|
|
|
|
results[host] = self.stats.summarize(host)
|
2012-02-25 21:21:11 +01:00
|
|
|
return results
|
2012-02-24 05:26:16 +01:00
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-04-26 20:35:19 +02:00
|
|
|
def _async_poll(self, poller, async_seconds, async_poll_interval):
|
2012-03-24 01:51:15 +01:00
|
|
|
''' launch an async job, if poll_interval is set, wait for completion '''
|
|
|
|
|
2012-04-26 20:35:19 +02:00
|
|
|
results = poller.wait(async_seconds, async_poll_interval)
|
2012-03-24 01:51:15 +01:00
|
|
|
|
2012-03-26 01:05:27 +02:00
|
|
|
# mark any hosts that are still listed as started as failed
|
|
|
|
# since these likely got killed by async_wrapper
|
2012-04-26 20:35:19 +02:00
|
|
|
for host in poller.hosts_to_poll:
|
|
|
|
reason = { 'failed' : 1, 'rc' : None, 'msg' : 'timed out' }
|
2013-08-06 18:42:30 +02:00
|
|
|
self.runner_callbacks.on_async_failed(host, reason, poller.jid)
|
2012-04-26 20:35:19 +02:00
|
|
|
results['contacted'][host] = reason
|
2012-03-24 01:51:15 +01:00
|
|
|
|
2012-03-13 01:53:10 +01:00
|
|
|
return results
|
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2014-01-07 03:16:43 +01:00
|
|
|
def _trim_unavailable_hosts(self, hostlist=[]):
|
2012-11-28 01:03:18 +01:00
|
|
|
''' returns a list of hosts that haven't failed and aren't dark '''
|
|
|
|
|
2014-02-03 17:10:45 +01:00
|
|
|
return [ h for h in self.inventory.list_hosts(hostlist) if (h not in self.stats.failures) and (h not in self.stats.dark)]
|
2012-11-28 01:03:18 +01:00
|
|
|
|
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
def _run_task_internal(self, task):
|
2012-03-03 03:43:46 +01:00
|
|
|
''' run a particular module step in a playbook '''
|
2012-03-24 01:51:15 +01:00
|
|
|
|
2014-01-07 03:16:43 +01:00
|
|
|
hosts = self._trim_unavailable_hosts(task.play._play_hosts)
|
2012-04-13 14:39:54 +02:00
|
|
|
self.inventory.restrict_to(hosts)
|
2012-03-26 01:05:27 +02:00
|
|
|
|
2012-03-13 01:53:10 +01:00
|
|
|
runner = ansible.runner.Runner(
|
2014-01-21 02:19:03 +01:00
|
|
|
pattern=task.play.hosts,
|
|
|
|
inventory=self.inventory,
|
|
|
|
module_name=task.module_name,
|
|
|
|
module_args=task.module_args,
|
|
|
|
forks=self.forks,
|
|
|
|
remote_pass=self.remote_pass,
|
|
|
|
module_path=self.module_path,
|
|
|
|
timeout=self.timeout,
|
|
|
|
remote_user=task.remote_user,
|
|
|
|
remote_port=task.play.remote_port,
|
|
|
|
module_vars=task.module_vars,
|
|
|
|
default_vars=task.default_vars,
|
|
|
|
private_key_file=self.private_key_file,
|
|
|
|
setup_cache=self.SETUP_CACHE,
|
|
|
|
basedir=task.play.basedir,
|
|
|
|
conditional=task.when,
|
|
|
|
callbacks=self.runner_callbacks,
|
|
|
|
sudo=task.sudo,
|
|
|
|
sudo_user=task.sudo_user,
|
|
|
|
transport=task.transport,
|
|
|
|
sudo_pass=task.sudo_pass,
|
|
|
|
is_playbook=True,
|
|
|
|
check=self.check,
|
|
|
|
diff=self.diff,
|
|
|
|
environment=task.environment,
|
|
|
|
complex_args=task.args,
|
|
|
|
accelerate=task.play.accelerate,
|
|
|
|
accelerate_port=task.play.accelerate_port,
|
2013-11-04 23:20:03 +01:00
|
|
|
accelerate_ipv6=task.play.accelerate_ipv6,
|
2014-01-21 02:19:03 +01:00
|
|
|
error_on_undefined_vars=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR,
|
|
|
|
su=task.su,
|
|
|
|
su_user=task.su_user,
|
2014-01-21 19:35:06 +01:00
|
|
|
su_pass=task.su_pass,
|
2014-02-11 18:03:11 +01:00
|
|
|
vault_pass = self.vault_password,
|
2014-01-31 23:09:10 +01:00
|
|
|
run_hosts=hosts,
|
|
|
|
no_log=task.no_log,
|
2012-03-13 01:53:10 +01:00
|
|
|
)
|
|
|
|
|
2014-01-26 00:43:15 +01:00
|
|
|
runner.module_vars.update({'play_hosts': hosts})
|
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
if task.async_seconds == 0:
|
2012-04-13 14:39:54 +02:00
|
|
|
results = runner.run()
|
2012-03-13 01:53:10 +01:00
|
|
|
else:
|
2012-05-26 06:37:34 +02:00
|
|
|
results, poller = runner.run_async(task.async_seconds)
|
2012-04-26 20:35:19 +02:00
|
|
|
self.stats.compute(results)
|
2012-05-26 06:37:34 +02:00
|
|
|
if task.async_poll_interval > 0:
|
|
|
|
# if not polling, playbook requested fire and forget, so don't poll
|
|
|
|
results = self._async_poll(poller, task.async_seconds, task.async_poll_interval)
|
2013-08-06 18:42:30 +02:00
|
|
|
else:
|
|
|
|
for (host, res) in results.get('contacted', {}).iteritems():
|
|
|
|
self.runner_callbacks.on_async_ok(host, res, poller.jid)
|
2012-04-13 14:39:54 +02:00
|
|
|
|
2012-10-01 02:48:35 +02:00
|
|
|
contacted = results.get('contacted',{})
|
|
|
|
dark = results.get('dark', {})
|
|
|
|
|
2012-04-13 14:39:54 +02:00
|
|
|
self.inventory.lift_restriction()
|
2012-10-01 02:48:35 +02:00
|
|
|
|
|
|
|
if len(contacted.keys()) == 0 and len(dark.keys()) == 0:
|
|
|
|
return None
|
|
|
|
|
2012-04-13 14:39:54 +02:00
|
|
|
return results
|
2012-03-14 02:30:34 +01:00
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
2012-03-03 03:43:46 +01:00
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
def _run_task(self, play, task, is_handler):
|
2012-03-24 01:51:15 +01:00
|
|
|
''' run a single task in the playbook and recursively run any subtasks. '''
|
2012-02-24 07:02:24 +01:00
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
ansible.callbacks.set_task(self.callbacks, task)
|
|
|
|
ansible.callbacks.set_task(self.runner_callbacks, task)
|
2013-03-26 03:56:32 +01:00
|
|
|
|
2013-09-20 22:46:34 +02:00
|
|
|
if task.role_name:
|
2013-10-01 04:51:39 +02:00
|
|
|
name = '%s | %s' % (task.role_name, task.name)
|
2013-09-20 22:46:34 +02:00
|
|
|
else:
|
|
|
|
name = task.name
|
|
|
|
|
2013-10-30 15:50:16 +01:00
|
|
|
self.callbacks.on_task_start(template(play.basedir, name, task.module_vars, lookup_fatal=False, filter_fatal=False), is_handler)
|
2013-03-11 05:39:05 +01:00
|
|
|
if hasattr(self.callbacks, 'skip_task') and self.callbacks.skip_task:
|
2013-06-09 18:17:57 +02:00
|
|
|
ansible.callbacks.set_task(self.callbacks, None)
|
|
|
|
ansible.callbacks.set_task(self.runner_callbacks, None)
|
2013-03-11 05:39:05 +01:00
|
|
|
return True
|
2013-06-25 07:33:29 +02:00
|
|
|
|
2014-03-12 02:30:58 +01:00
|
|
|
# template ignore_errors
|
|
|
|
cond = template(play.basedir, task.ignore_errors, task.module_vars, expand_lists=False)
|
|
|
|
task.ignore_errors = utils.check_conditional(cond , play.basedir, task.module_vars, fail_on_undefined=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR)
|
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
# load up an appropriate ansible runner to run the task in parallel
|
|
|
|
results = self._run_task_internal(task)
|
2012-10-01 02:48:35 +02:00
|
|
|
|
2012-07-15 17:20:59 +02:00
|
|
|
# if no hosts are matched, carry on
|
2012-10-01 02:48:35 +02:00
|
|
|
hosts_remaining = True
|
2012-07-15 17:20:59 +02:00
|
|
|
if results is None:
|
2012-10-01 02:48:35 +02:00
|
|
|
hosts_remaining = False
|
2012-07-15 17:20:59 +02:00
|
|
|
results = {}
|
2013-06-25 07:33:29 +02:00
|
|
|
|
2012-10-01 02:48:35 +02:00
|
|
|
contacted = results.get('contacted', {})
|
2012-09-04 22:40:07 +02:00
|
|
|
self.stats.compute(results, ignore_errors=task.ignore_errors)
|
2012-07-26 03:30:49 +02:00
|
|
|
|
|
|
|
# add facts to the global setup cache
|
2012-10-01 02:48:35 +02:00
|
|
|
for host, result in contacted.iteritems():
|
2013-08-09 04:08:46 +02:00
|
|
|
if 'results' in result:
|
|
|
|
# task ran with_ lookup plugin, so facts are encapsulated in
|
|
|
|
# multiple list items in the results key
|
|
|
|
for res in result['results']:
|
|
|
|
if type(res) == dict:
|
|
|
|
facts = res.get('ansible_facts', {})
|
|
|
|
self.SETUP_CACHE[host].update(facts)
|
|
|
|
else:
|
|
|
|
facts = result.get('ansible_facts', {})
|
|
|
|
self.SETUP_CACHE[host].update(facts)
|
2013-01-30 17:56:04 +01:00
|
|
|
# extra vars need to always trump - so update again following the facts
|
|
|
|
self.SETUP_CACHE[host].update(self.extra_vars)
|
2012-08-10 07:13:37 +02:00
|
|
|
if task.register:
|
2013-07-14 21:07:45 +02:00
|
|
|
if 'stdout' in result and 'stdout_lines' not in result:
|
2012-09-17 22:57:33 +02:00
|
|
|
result['stdout_lines'] = result['stdout'].splitlines()
|
2012-08-10 07:13:37 +02:00
|
|
|
self.SETUP_CACHE[host][task.register] = result
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2013-04-18 04:27:00 +02:00
|
|
|
# also have to register some failed, but ignored, tasks
|
|
|
|
if task.ignore_errors and task.register:
|
|
|
|
failed = results.get('failed', {})
|
|
|
|
for host, result in failed.iteritems():
|
2013-07-14 21:07:45 +02:00
|
|
|
if 'stdout' in result and 'stdout_lines' not in result:
|
2013-04-18 04:27:00 +02:00
|
|
|
result['stdout_lines'] = result['stdout'].splitlines()
|
|
|
|
self.SETUP_CACHE[host][task.register] = result
|
|
|
|
|
2012-02-25 20:42:41 +01:00
|
|
|
# flag which notify handlers need to be run
|
2012-05-26 06:37:34 +02:00
|
|
|
if len(task.notify) > 0:
|
2012-03-24 01:51:15 +01:00
|
|
|
for host, results in results.get('contacted',{}).iteritems():
|
2012-02-25 23:16:23 +01:00
|
|
|
if results.get('changed', False):
|
2012-05-26 06:37:34 +02:00
|
|
|
for handler_name in task.notify:
|
2013-04-23 04:03:39 +02:00
|
|
|
self._flag_handler(play, template(play.basedir, handler_name, task.module_vars), host)
|
2012-02-24 07:02:24 +01:00
|
|
|
|
2013-06-09 18:17:57 +02:00
|
|
|
ansible.callbacks.set_task(self.callbacks, None)
|
|
|
|
ansible.callbacks.set_task(self.runner_callbacks, None)
|
2012-10-01 02:48:35 +02:00
|
|
|
return hosts_remaining
|
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-11-23 00:31:38 +01:00
|
|
|
def _flag_handler(self, play, handler_name, host):
|
2012-08-07 02:07:02 +02:00
|
|
|
'''
|
2012-02-25 20:42:41 +01:00
|
|
|
if a task has any notify elements, flag handlers for run
|
|
|
|
at end of execution cycle for hosts that have indicated
|
|
|
|
changes have been made
|
|
|
|
'''
|
2012-02-27 02:18:42 +01:00
|
|
|
|
2012-03-27 03:31:48 +02:00
|
|
|
found = False
|
2012-11-23 00:31:38 +01:00
|
|
|
for x in play.handlers():
|
2013-04-23 04:03:39 +02:00
|
|
|
if handler_name == template(play.basedir, x.name, x.module_vars):
|
2012-03-27 03:31:48 +02:00
|
|
|
found = True
|
2012-05-26 06:37:34 +02:00
|
|
|
self.callbacks.on_notify(host, x.name)
|
|
|
|
x.notified_by.append(host)
|
2012-03-27 03:31:48 +02:00
|
|
|
if not found:
|
2012-05-26 06:37:34 +02:00
|
|
|
raise errors.AnsibleError("change handler (%s) is not defined" % handler_name)
|
2012-02-24 07:02:24 +01:00
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-07-14 18:24:19 +02:00
|
|
|
def _do_setup_step(self, play):
|
|
|
|
''' get facts from the remote system '''
|
2012-08-07 02:07:02 +02:00
|
|
|
|
2014-03-03 16:20:42 +01:00
|
|
|
host_list = self._trim_unavailable_hosts(play._play_hosts)
|
2012-03-20 03:42:31 +01:00
|
|
|
|
2014-03-15 21:19:28 +01:00
|
|
|
if play.gather_facts is None and C.DEFAULT_GATHERING == 'smart':
|
|
|
|
host_list = [h for h in host_list if h not in self.SETUP_CACHE or 'module_setup' not in self.SETUP_CACHE[h]]
|
|
|
|
if len(host_list) == 0:
|
|
|
|
return {}
|
2014-03-15 23:10:39 +01:00
|
|
|
elif play.gather_facts is False or (play.gather_facts is None and C.DEFAULT_GATHERING == 'explicit'):
|
2014-03-15 21:19:28 +01:00
|
|
|
return {}
|
|
|
|
|
2012-07-14 18:24:19 +02:00
|
|
|
self.callbacks.on_setup()
|
2012-04-13 14:39:54 +02:00
|
|
|
self.inventory.restrict_to(host_list)
|
2013-06-25 07:33:29 +02:00
|
|
|
|
2013-04-20 15:09:35 +02:00
|
|
|
ansible.callbacks.set_task(self.callbacks, None)
|
|
|
|
ansible.callbacks.set_task(self.runner_callbacks, None)
|
2012-03-26 01:05:27 +02:00
|
|
|
|
2012-03-06 02:09:03 +01:00
|
|
|
# push any variables down to the system
|
|
|
|
setup_results = ansible.runner.Runner(
|
2014-01-21 02:19:03 +01:00
|
|
|
pattern=play.hosts,
|
|
|
|
module_name='setup',
|
|
|
|
module_args={},
|
|
|
|
inventory=self.inventory,
|
|
|
|
forks=self.forks,
|
|
|
|
module_path=self.module_path,
|
|
|
|
timeout=self.timeout,
|
|
|
|
remote_user=play.remote_user,
|
|
|
|
remote_pass=self.remote_pass,
|
|
|
|
remote_port=play.remote_port,
|
|
|
|
private_key_file=self.private_key_file,
|
|
|
|
setup_cache=self.SETUP_CACHE,
|
|
|
|
callbacks=self.runner_callbacks,
|
|
|
|
sudo=play.sudo,
|
|
|
|
sudo_user=play.sudo_user,
|
|
|
|
sudo_pass=self.sudo_pass,
|
2014-01-27 23:26:31 +01:00
|
|
|
su=play.su,
|
|
|
|
su_user=play.su_user,
|
|
|
|
su_pass=self.su_pass,
|
2014-02-11 18:03:11 +01:00
|
|
|
vault_pass=self.vault_password,
|
2014-01-27 23:26:31 +01:00
|
|
|
transport=play.transport,
|
2014-01-21 02:19:03 +01:00
|
|
|
is_playbook=True,
|
|
|
|
module_vars=play.vars,
|
|
|
|
default_vars=play.default_vars,
|
|
|
|
check=self.check,
|
|
|
|
diff=self.diff,
|
|
|
|
accelerate=play.accelerate,
|
|
|
|
accelerate_port=play.accelerate_port,
|
2012-03-06 02:09:03 +01:00
|
|
|
).run()
|
2012-03-26 01:05:27 +02:00
|
|
|
self.stats.compute(setup_results, setup=True)
|
2012-03-14 02:30:34 +01:00
|
|
|
|
2012-04-13 14:39:54 +02:00
|
|
|
self.inventory.lift_restriction()
|
|
|
|
|
2012-03-06 02:09:03 +01:00
|
|
|
# now for each result, load into the setup cache so we can
|
|
|
|
# let runner template out future commands
|
|
|
|
setup_ok = setup_results.get('contacted', {})
|
2012-07-14 18:24:19 +02:00
|
|
|
for (host, result) in setup_ok.iteritems():
|
2014-03-16 19:10:57 +01:00
|
|
|
self.SETUP_CACHE[host].update({'module_setup': True})
|
2012-09-28 17:44:51 +02:00
|
|
|
self.SETUP_CACHE[host].update(result.get('ansible_facts', {}))
|
2012-05-26 06:37:34 +02:00
|
|
|
return setup_results
|
2012-03-20 03:42:31 +01:00
|
|
|
|
2012-03-24 01:51:15 +01:00
|
|
|
# *****************************************************
|
|
|
|
|
2013-04-08 05:37:10 +02:00
|
|
|
|
|
|
|
def generate_retry_inventory(self, replay_hosts):
|
|
|
|
'''
|
2013-06-25 07:33:29 +02:00
|
|
|
called by /usr/bin/ansible when a playbook run fails. It generates a inventory
|
2013-04-08 05:37:10 +02:00
|
|
|
that allows re-running on ONLY the failed hosts. This may duplicate some
|
|
|
|
variable information in group_vars/host_vars but that is ok, and expected.
|
2013-06-25 07:33:29 +02:00
|
|
|
'''
|
2013-04-08 05:37:10 +02:00
|
|
|
|
|
|
|
buf = StringIO.StringIO()
|
2013-04-08 18:36:01 +02:00
|
|
|
for x in replay_hosts:
|
|
|
|
buf.write("%s\n" % x)
|
|
|
|
basedir = self.inventory.basedir()
|
|
|
|
filename = "%s.retry" % os.path.basename(self.filename)
|
2013-04-08 18:38:05 +02:00
|
|
|
filename = filename.replace(".yml","")
|
2013-08-20 19:22:48 +02:00
|
|
|
filename = os.path.join(os.path.expandvars('$HOME/'), filename)
|
2013-04-08 05:37:10 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
fd = open(filename, 'w')
|
|
|
|
fd.write(buf.getvalue())
|
|
|
|
fd.close()
|
|
|
|
return filename
|
2013-04-08 18:36:01 +02:00
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return None
|
2013-04-08 05:37:10 +02:00
|
|
|
|
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
def _run_play(self, play):
|
2012-03-24 01:51:15 +01:00
|
|
|
''' run a list of tasks for a given pattern, in order '''
|
2014-03-24 15:28:48 +01:00
|
|
|
|
2012-05-26 06:37:34 +02:00
|
|
|
self.callbacks.on_play_start(play.name)
|
2014-01-07 03:16:43 +01:00
|
|
|
# Get the hosts for this play
|
|
|
|
play._play_hosts = self.inventory.list_hosts(play.hosts)
|
2012-10-02 15:15:55 +02:00
|
|
|
# if no hosts matches this play, drop out
|
2014-01-07 03:16:43 +01:00
|
|
|
if not play._play_hosts:
|
2012-10-02 15:15:55 +02:00
|
|
|
self.callbacks.on_no_hosts_matched()
|
|
|
|
return True
|
|
|
|
|
2012-07-14 18:24:19 +02:00
|
|
|
# get facts from system
|
2012-08-14 05:36:51 +02:00
|
|
|
self._do_setup_step(play)
|
2012-05-26 06:37:34 +02:00
|
|
|
|
2012-03-20 03:42:31 +01:00
|
|
|
# now with that data, handle contentional variable file imports!
|
2014-01-07 03:16:43 +01:00
|
|
|
all_hosts = self._trim_unavailable_hosts(play._play_hosts)
|
2014-02-11 18:03:11 +01:00
|
|
|
play.update_vars_files(all_hosts, vault_password=self.vault_password)
|
2014-01-07 03:16:43 +01:00
|
|
|
hosts_count = len(all_hosts)
|
2012-08-18 15:52:13 +02:00
|
|
|
|
|
|
|
serialized_batch = []
|
|
|
|
if play.serial <= 0:
|
|
|
|
serialized_batch = [all_hosts]
|
|
|
|
else:
|
|
|
|
# do N forks all the way through before moving to next
|
|
|
|
while len(all_hosts) > 0:
|
|
|
|
play_hosts = []
|
2012-08-21 22:43:05 +02:00
|
|
|
for x in range(play.serial):
|
2012-08-18 15:52:13 +02:00
|
|
|
if len(all_hosts) > 0:
|
|
|
|
play_hosts.append(all_hosts.pop())
|
2012-08-26 05:10:43 +02:00
|
|
|
serialized_batch.append(play_hosts)
|
2012-08-18 15:52:13 +02:00
|
|
|
|
2014-03-25 05:20:11 +01:00
|
|
|
task_errors = False
|
2012-08-18 15:52:13 +02:00
|
|
|
for on_hosts in serialized_batch:
|
|
|
|
|
2014-01-07 03:16:43 +01:00
|
|
|
# restrict the play to just the hosts we have in our on_hosts block that are
|
|
|
|
# available.
|
|
|
|
play._play_hosts = self._trim_unavailable_hosts(on_hosts)
|
2012-08-18 15:52:13 +02:00
|
|
|
self.inventory.also_restrict_to(on_hosts)
|
|
|
|
|
|
|
|
for task in play.tasks():
|
2013-04-21 00:03:03 +02:00
|
|
|
|
2014-03-24 15:28:48 +01:00
|
|
|
# skip handlers until play is finished
|
2013-04-21 00:03:03 +02:00
|
|
|
if task.meta is not None:
|
2014-03-24 15:28:48 +01:00
|
|
|
continue
|
2013-04-21 00:03:03 +02:00
|
|
|
|
2012-08-18 15:52:13 +02:00
|
|
|
# only run the task if the requested tags match
|
|
|
|
should_run = False
|
|
|
|
for x in self.only_tags:
|
2013-04-21 00:03:03 +02:00
|
|
|
|
2012-08-18 15:52:13 +02:00
|
|
|
for y in task.tags:
|
2013-06-25 07:33:29 +02:00
|
|
|
if x == y:
|
2012-08-18 15:52:13 +02:00
|
|
|
should_run = True
|
|
|
|
break
|
2013-04-21 00:03:03 +02:00
|
|
|
|
2013-06-25 07:33:29 +02:00
|
|
|
# Check for tags that we need to skip
|
2012-08-18 15:52:13 +02:00
|
|
|
if should_run:
|
2013-06-25 07:33:29 +02:00
|
|
|
if any(x in task.tags for x in self.skip_tags):
|
|
|
|
should_run = False
|
|
|
|
|
|
|
|
if should_run:
|
|
|
|
|
2012-10-01 02:48:35 +02:00
|
|
|
if not self._run_task(play, task, False):
|
2012-10-02 04:07:08 +02:00
|
|
|
# whether no hosts matched is fatal or not depends if it was on the initial step.
|
|
|
|
# if we got exactly no hosts on the first step (setup!) then the host group
|
|
|
|
# just didn't match anything and that's ok
|
2012-10-02 15:15:55 +02:00
|
|
|
return False
|
|
|
|
|
2014-01-07 03:16:43 +01:00
|
|
|
# Get a new list of what hosts are left as available, the ones that
|
|
|
|
# did not go fail/dark during the task
|
|
|
|
host_list = self._trim_unavailable_hosts(play._play_hosts)
|
2012-10-02 15:15:55 +02:00
|
|
|
|
2013-09-07 07:37:17 +02:00
|
|
|
# Set max_fail_pct to 0, So if any hosts fails, bail out
|
|
|
|
if task.any_errors_fatal and len(host_list) < hosts_count:
|
|
|
|
play.max_fail_pct = 0
|
|
|
|
|
|
|
|
# If threshold for max nodes failed is exceeded , bail out.
|
2014-03-21 18:31:47 +01:00
|
|
|
if play.serial > 0:
|
|
|
|
# if serial is set, we need to shorten the size of host_count
|
|
|
|
play_count = len(play._play_hosts)
|
|
|
|
if (play_count - len(host_list)) > int((play.max_fail_pct)/100.0 * play_count):
|
|
|
|
host_list = None
|
|
|
|
else:
|
|
|
|
if (hosts_count - len(host_list)) > int((play.max_fail_pct)/100.0 * hosts_count):
|
|
|
|
host_list = None
|
2013-11-07 19:54:06 +01:00
|
|
|
|
2012-10-02 15:15:55 +02:00
|
|
|
# if no hosts remain, drop out
|
|
|
|
if not host_list:
|
2014-03-24 15:28:48 +01:00
|
|
|
if self.force_handlers:
|
2014-03-25 15:35:05 +01:00
|
|
|
task_errors = True
|
2014-03-25 05:20:11 +01:00
|
|
|
break
|
2014-03-24 15:28:48 +01:00
|
|
|
else:
|
|
|
|
return False
|
2012-10-02 15:15:55 +02:00
|
|
|
|
2014-03-25 05:20:11 +01:00
|
|
|
if task_errors and not self.force_handlers:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
self.inventory.lift_also_restriction()
|
|
|
|
if not self.run_handlers(play):
|
|
|
|
return False
|
|
|
|
|
2012-10-01 03:06:00 +02:00
|
|
|
return True
|
2012-08-18 15:52:13 +02:00
|
|
|
|
2014-03-24 15:28:48 +01:00
|
|
|
|
|
|
|
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
|