2012-02-29 00:08:09 +00:00
|
|
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
2012-02-24 04:26:16 +00:00
|
|
|
#
|
2012-02-29 00:08:09 +00:00
|
|
|
# This file is part of Ansible
|
2012-02-24 04:26:16 +00:00
|
|
|
#
|
2012-02-29 00:08:09 +00: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 04:26:16 +00:00
|
|
|
|
2012-03-03 15:53:15 +00:00
|
|
|
#############################################
|
|
|
|
|
2012-04-13 12:39:54 +00:00
|
|
|
import ansible.inventory
|
2012-02-24 04:28:58 +00:00
|
|
|
import ansible.runner
|
2012-02-24 04:26:16 +00:00
|
|
|
import ansible.constants as C
|
2012-03-18 21:16:12 +00:00
|
|
|
from ansible import utils
|
|
|
|
from ansible import errors
|
2012-03-03 15:53:15 +00:00
|
|
|
import os
|
2012-05-26 04:37:34 +00:00
|
|
|
from play import Play
|
2012-03-06 01:09:03 +00:00
|
|
|
|
2012-03-03 15:53:15 +00:00
|
|
|
#############################################
|
2012-02-24 04:26:16 +00:00
|
|
|
|
|
|
|
class PlayBook(object):
|
2012-03-03 03:03:03 +00:00
|
|
|
|
|
|
|
'''
|
2012-02-24 04:26:16 +00:00
|
|
|
runs an ansible playbook, given as a datastructure
|
2012-02-24 06:02:24 +00:00
|
|
|
or YAML filename. a playbook is a deployment, config
|
|
|
|
management, or automation based set of commands to
|
|
|
|
run in series.
|
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
multiple plays/tasks do not execute simultaneously,
|
2012-02-24 06:02:24 +00:00
|
|
|
but tasks in each pattern do execute in parallel
|
2012-03-24 00:51:15 +00:00
|
|
|
(according to the number of forks requested) among
|
|
|
|
the hosts they address
|
2012-02-24 04:26:16 +00:00
|
|
|
'''
|
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-03-03 03:03:03 +00:00
|
|
|
def __init__(self,
|
2012-03-25 23:05:27 +00:00
|
|
|
playbook = None,
|
|
|
|
host_list = C.DEFAULT_HOST_LIST,
|
|
|
|
module_path = C.DEFAULT_MODULE_PATH,
|
|
|
|
forks = C.DEFAULT_FORKS,
|
|
|
|
timeout = C.DEFAULT_TIMEOUT,
|
|
|
|
remote_user = C.DEFAULT_REMOTE_USER,
|
|
|
|
remote_pass = C.DEFAULT_REMOTE_PASS,
|
2012-04-13 23:06:11 +00:00
|
|
|
sudo_pass = C.DEFAULT_SUDO_PASS,
|
2012-03-28 21:05:31 +00:00
|
|
|
remote_port = C.DEFAULT_REMOTE_PORT,
|
2012-04-11 16:39:04 +00:00
|
|
|
transport = C.DEFAULT_TRANSPORT,
|
2012-05-14 20:52:48 +00:00
|
|
|
private_key_file = C.DEFAULT_PRIVATE_KEY_FILE,
|
2012-03-30 20:17:16 +00:00
|
|
|
debug = False,
|
2012-03-25 23:05:27 +00:00
|
|
|
callbacks = None,
|
|
|
|
runner_callbacks = None,
|
2012-04-17 02:15:55 +00:00
|
|
|
stats = None,
|
2012-04-26 23:56:10 +00:00
|
|
|
sudo = False,
|
2012-05-07 15:37:50 +00:00
|
|
|
sudo_user = C.DEFAULT_SUDO_USER,
|
2012-04-26 23:56:10 +00:00
|
|
|
extra_vars = None):
|
2012-03-25 23:05:27 +00:00
|
|
|
|
2012-04-17 03:45:15 +00: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-05-26 04:37:34 +00:00
|
|
|
self.SETUP_CACHE = {}
|
|
|
|
|
2012-03-25 23:05:27 +00:00
|
|
|
if playbook is None or callbacks is None or runner_callbacks is None or stats is None:
|
|
|
|
raise Exception('missing required arguments')
|
|
|
|
|
2012-04-26 23:56:10 +00:00
|
|
|
if extra_vars is None:
|
|
|
|
extra_vars = {}
|
|
|
|
|
2012-03-25 23:05:27 +00: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 21:05:31 +00:00
|
|
|
self.remote_port = remote_port
|
2012-04-11 16:39:04 +00:00
|
|
|
self.transport = transport
|
2012-03-30 20:17:16 +00:00
|
|
|
self.debug = debug
|
2012-03-25 23:05:27 +00:00
|
|
|
self.callbacks = callbacks
|
|
|
|
self.runner_callbacks = runner_callbacks
|
|
|
|
self.stats = stats
|
2012-04-17 02:15:55 +00:00
|
|
|
self.sudo = sudo
|
2012-04-13 23:06:11 +00:00
|
|
|
self.sudo_pass = sudo_pass
|
2012-05-06 22:24:04 +00:00
|
|
|
self.sudo_user = sudo_user
|
2012-04-26 23:56:10 +00:00
|
|
|
self.extra_vars = extra_vars
|
2012-05-02 22:29:46 +00:00
|
|
|
self.global_vars = {}
|
2012-05-14 20:52:48 +00:00
|
|
|
self.private_key_file = private_key_file
|
2012-03-03 15:53:15 +00:00
|
|
|
|
2012-05-08 03:16:20 +00:00
|
|
|
self.inventory = ansible.inventory.Inventory(host_list)
|
|
|
|
|
|
|
|
if not self.inventory._is_script:
|
|
|
|
self.global_vars.update(self.inventory.get_group_variables('all'))
|
2012-05-02 22:29:46 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
self.basedir = os.path.dirname(playbook)
|
|
|
|
self.playbook = utils.parse_yaml_from_file(playbook)
|
2012-03-24 00:51:15 +00:00
|
|
|
|
|
|
|
# *****************************************************
|
2012-02-24 06:02:24 +00:00
|
|
|
|
|
|
|
def run(self):
|
2012-02-27 01:18:42 +00:00
|
|
|
''' run all patterns in the playbook '''
|
2012-02-24 06:02:24 +00:00
|
|
|
|
2012-02-27 01:18:42 +00:00
|
|
|
# loop through all patterns and run them
|
2012-03-07 00:24:36 +00:00
|
|
|
self.callbacks.on_start()
|
2012-05-26 04:37:34 +00:00
|
|
|
for play_ds in self.playbook:
|
|
|
|
self._run_play(Play(self,play_ds))
|
2012-02-25 20:21:11 +00:00
|
|
|
|
2012-02-27 01:18:42 +00:00
|
|
|
# summarize the results
|
2012-02-25 20:21:11 +00:00
|
|
|
results = {}
|
2012-03-25 23:05:27 +00:00
|
|
|
for host in self.stats.processed.keys():
|
|
|
|
results[host] = self.stats.summarize(host)
|
2012-02-25 20:21:11 +00:00
|
|
|
return results
|
2012-02-24 04:26:16 +00:00
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-04-26 18:35:19 +00:00
|
|
|
def _async_poll(self, poller, async_seconds, async_poll_interval):
|
2012-03-24 00:51:15 +00:00
|
|
|
''' launch an async job, if poll_interval is set, wait for completion '''
|
|
|
|
|
2012-04-26 18:35:19 +00:00
|
|
|
results = poller.wait(async_seconds, async_poll_interval)
|
2012-03-24 00:51:15 +00:00
|
|
|
|
2012-03-25 23:05:27 +00:00
|
|
|
# mark any hosts that are still listed as started as failed
|
|
|
|
# since these likely got killed by async_wrapper
|
2012-04-26 18:35:19 +00:00
|
|
|
for host in poller.hosts_to_poll:
|
|
|
|
reason = { 'failed' : 1, 'rc' : None, 'msg' : 'timed out' }
|
|
|
|
self.runner_callbacks.on_failed(host, reason)
|
|
|
|
results['contacted'][host] = reason
|
2012-03-24 00:51:15 +00:00
|
|
|
|
2012-03-13 00:53:10 +00:00
|
|
|
return results
|
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
def _run_task_internal(self, task):
|
2012-03-03 02:43:46 +00:00
|
|
|
''' run a particular module step in a playbook '''
|
2012-03-24 00:51:15 +00:00
|
|
|
|
2012-04-13 12:39:54 +00:00
|
|
|
hosts = [ h for h in self.inventory.list_hosts() if (h not in self.stats.failures) and (h not in self.stats.dark)]
|
|
|
|
self.inventory.restrict_to(hosts)
|
2012-03-25 23:05:27 +00:00
|
|
|
|
2012-03-13 00:53:10 +00:00
|
|
|
runner = ansible.runner.Runner(
|
2012-05-26 04:37:34 +00:00
|
|
|
pattern=task.play.hosts, inventory=self.inventory, module_name=task.module_name,
|
|
|
|
module_args=task.module_args, forks=self.forks,
|
2012-03-24 00:51:15 +00:00
|
|
|
remote_pass=self.remote_pass, module_path=self.module_path,
|
2012-05-26 04:37:34 +00:00
|
|
|
timeout=self.timeout, remote_user=task.play.remote_user,
|
|
|
|
remote_port=task.play.remote_port, module_vars=task.module_vars,
|
2012-05-14 20:52:48 +00:00
|
|
|
private_key_file=self.private_key_file,
|
2012-05-26 04:37:34 +00:00
|
|
|
setup_cache=self.SETUP_CACHE, basedir=self.basedir,
|
|
|
|
conditional=task.only_if, callbacks=self.runner_callbacks,
|
|
|
|
debug=self.debug, sudo=task.play.sudo, sudo_user=task.play.sudo_user,
|
|
|
|
transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True
|
2012-03-13 00:53:10 +00:00
|
|
|
)
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
if task.async_seconds == 0:
|
2012-04-13 12:39:54 +00:00
|
|
|
results = runner.run()
|
2012-03-13 00:53:10 +00:00
|
|
|
else:
|
2012-05-26 04:37:34 +00:00
|
|
|
results, poller = runner.run_async(task.async_seconds)
|
2012-04-26 18:35:19 +00:00
|
|
|
self.stats.compute(results)
|
2012-05-26 04:37:34 +00: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)
|
2012-04-13 12:39:54 +00:00
|
|
|
|
|
|
|
self.inventory.lift_restriction()
|
|
|
|
return results
|
2012-03-14 01:30:34 +00:00
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
2012-03-03 02:43:46 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
def _run_task(self, play, task, is_handler):
|
2012-03-24 00:51:15 +00:00
|
|
|
''' run a single task in the playbook and recursively run any subtasks. '''
|
2012-02-24 06:02:24 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
self.callbacks.on_task_start(task.name, is_handler)
|
2012-02-24 07:36:38 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
# load up an appropriate ansible runner to run the task in parallel
|
|
|
|
results = self._run_task_internal(task)
|
2012-03-02 03:10:47 +00:00
|
|
|
|
2012-04-23 18:06:14 +00:00
|
|
|
# add facts to the global setup cache
|
|
|
|
for host, result in results['contacted'].iteritems():
|
|
|
|
if "ansible_facts" in result:
|
|
|
|
for k,v in result['ansible_facts'].iteritems():
|
2012-05-26 04:37:34 +00:00
|
|
|
self.SETUP_CACHE[host][k]=v
|
2012-04-23 18:06:14 +00:00
|
|
|
|
2012-03-25 23:05:27 +00:00
|
|
|
self.stats.compute(results)
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
# if no hosts are matched, carry on
|
2012-03-02 03:10:47 +00:00
|
|
|
if results is None:
|
|
|
|
results = {}
|
2012-02-24 06:02:24 +00:00
|
|
|
|
2012-02-25 19:42:41 +00:00
|
|
|
# flag which notify handlers need to be run
|
2012-05-26 04:37:34 +00:00
|
|
|
if len(task.notify) > 0:
|
2012-03-24 00:51:15 +00:00
|
|
|
for host, results in results.get('contacted',{}).iteritems():
|
2012-02-25 22:16:23 +00:00
|
|
|
if results.get('changed', False):
|
2012-05-26 04:37:34 +00:00
|
|
|
for handler_name in task.notify:
|
|
|
|
self._flag_handler(play.handlers(), handler_name, host)
|
2012-02-24 06:02:24 +00:00
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
def _flag_handler(self, handlers, handler_name, host):
|
2012-02-25 19:42:41 +00: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 01:18:42 +00:00
|
|
|
|
2012-03-27 01:31:48 +00:00
|
|
|
found = False
|
2012-02-25 19:42:41 +00:00
|
|
|
for x in handlers:
|
2012-05-26 04:37:34 +00:00
|
|
|
if handler_name == x.name:
|
2012-03-27 01:31:48 +00:00
|
|
|
found = True
|
2012-05-26 04:37:34 +00:00
|
|
|
self.callbacks.on_notify(host, x.name)
|
|
|
|
x.notified_by.append(host)
|
2012-03-27 01:31:48 +00:00
|
|
|
if not found:
|
2012-05-26 04:37:34 +00:00
|
|
|
raise errors.AnsibleError("change handler (%s) is not defined" % handler_name)
|
2012-02-24 06:02:24 +00:00
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
def _do_setup_step(self, play, vars_files=None):
|
2012-03-24 01:03:25 +00:00
|
|
|
|
2012-03-20 02:42:31 +00:00
|
|
|
''' push variables down to the systems and get variables+facts back up '''
|
|
|
|
|
|
|
|
# this enables conditional includes like $facter_os.yml and is only done
|
|
|
|
# after the original pass when we have that data.
|
|
|
|
#
|
|
|
|
|
|
|
|
if vars_files is not None:
|
|
|
|
self.callbacks.on_setup_secondary()
|
2012-05-26 04:37:34 +00:00
|
|
|
play.update_vars_files(self.inventory.list_hosts(play.hosts))
|
2012-03-20 02:42:31 +00:00
|
|
|
else:
|
|
|
|
self.callbacks.on_setup_primary()
|
2012-02-25 20:21:11 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
host_list = [ h for h in self.inventory.list_hosts(play.hosts)
|
|
|
|
if not (h in self.stats.failures or h in self.stats.dark) ]
|
2012-05-10 09:45:30 +00:00
|
|
|
|
2012-04-13 12:39:54 +00:00
|
|
|
self.inventory.restrict_to(host_list)
|
2012-03-25 23:05:27 +00:00
|
|
|
|
2012-03-06 01:09:03 +00:00
|
|
|
# push any variables down to the system
|
|
|
|
setup_results = ansible.runner.Runner(
|
2012-05-26 04:37:34 +00:00
|
|
|
pattern=play.hosts, module_name='setup', module_args=play.vars, 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,
|
|
|
|
debug=self.debug, transport=play.transport, sudo_pass=self.sudo_pass, is_playbook=True
|
2012-03-06 01:09:03 +00:00
|
|
|
).run()
|
2012-03-25 23:05:27 +00:00
|
|
|
self.stats.compute(setup_results, setup=True)
|
2012-03-14 01:30:34 +00:00
|
|
|
|
2012-04-13 12:39:54 +00:00
|
|
|
self.inventory.lift_restriction()
|
|
|
|
|
2012-03-06 01:09:03 +00: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-03-20 02:42:31 +00:00
|
|
|
if vars_files is None:
|
|
|
|
# first pass only or we'll erase good work
|
|
|
|
for (host, result) in setup_ok.iteritems():
|
2012-04-23 18:06:14 +00:00
|
|
|
if 'ansible_facts' in result:
|
2012-05-26 04:37:34 +00:00
|
|
|
self.SETUP_CACHE[host] = result['ansible_facts']
|
|
|
|
return setup_results
|
2012-03-20 02:42:31 +00:00
|
|
|
|
2012-03-24 00:51:15 +00:00
|
|
|
# *****************************************************
|
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
def _run_play(self, play):
|
2012-03-24 00:51:15 +00:00
|
|
|
''' run a list of tasks for a given pattern, in order '''
|
2012-03-20 02:42:31 +00:00
|
|
|
|
2012-05-26 04:37:34 +00:00
|
|
|
self.callbacks.on_play_start(play.name)
|
2012-03-20 02:42:31 +00:00
|
|
|
|
|
|
|
# push any variables down to the system # and get facts/ohai/other data back up
|
2012-05-26 04:37:34 +00:00
|
|
|
rc = self._do_setup_step(play) # pattern, vars, user, port, sudo, sudo_user, transport, None)
|
|
|
|
|
2012-03-20 02:42:31 +00:00
|
|
|
# now with that data, handle contentional variable file imports!
|
2012-05-26 04:37:34 +00:00
|
|
|
if len(play.vars_files) > 0:
|
|
|
|
rc = self._do_setup_step(play, play.vars_files)
|
2012-03-20 02:42:31 +00:00
|
|
|
|
2012-03-06 01:09:03 +00:00
|
|
|
# run all the top level tasks, these get run on every node
|
2012-05-26 04:37:34 +00:00
|
|
|
for task in play.tasks():
|
|
|
|
self._run_task(play, task, False)
|
|
|
|
|
|
|
|
# run notify actions
|
|
|
|
for handler in play.handlers():
|
|
|
|
if len(handler.notified_by) > 0:
|
|
|
|
self.inventory.restrict_to(handler.notified_by)
|
|
|
|
self._run_task(play, handler, True)
|
2012-04-13 12:39:54 +00:00
|
|
|
self.inventory.lift_restriction()
|
2012-02-24 04:26:16 +00:00
|
|
|
|