community.general/bin/ansible
Michael DeHaan db7ba87111 Add polling logic in runner such that all actions get kicked off everywhere then polling
happens only on needed hosts, allowing some hosts to fail and drop out of the running.
2012-03-11 20:54:54 -04:00

199 lines
7.3 KiB
Python
Executable file

#!/usr/bin/python
# (c) 2012, 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/>.
########################################################
try:
import json
except ImportError:
import simplejson as json
from optparse import OptionParser
import sys
import os
import getpass
import shlex
import time
from optparse import OptionParser
import ansible.runner
import ansible.playbook
import ansible.constants as C
from ansible.utils import *
########################################################
class Cli(object):
''' code behind bin/ansible '''
# ----------------------------------------------
def __init__(self):
pass
# ----------------------------------------------
def parse(self):
''' create an options parser for bin/ansible '''
parser = OptionParser(usage = 'ansible <host-pattern> [options]')
parser.add_option("-a", "--args", dest="module_args",
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
parser.add_option("-B", "--background", dest="seconds", type='int', default=0,
help="run asynchronously, failing after X seconds")
parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
help='number of parallel processes to use')
parser.add_option("-i", "--inventory-file", dest="inventory",
help="inventory host file", default=C.DEFAULT_HOST_LIST)
parser.add_option("-k", "--ask-pass", default=False, action="store_true",
help="ask for SSH password")
parser.add_option("-M", "--module-path", dest="module_path",
help="path to module library", default=C.DEFAULT_MODULE_PATH)
parser.add_option("-m", "--module-name", dest="module_name",
help="module name to execute", default=C.DEFAULT_MODULE_NAME)
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
help="condense output")
parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int',
dest='poll_interval', help="set the poll interval if using -B")
parser.add_option('-t', '--tree', dest='tree', default=None,
help="log output to this directory")
parser.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int',
dest='timeout', help="set the SSH timeout in seconds")
parser.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER,
dest='remote_user', help='connect as this user')
options, args = parser.parse_args()
if len(args) == 0 or len(args) > 1:
parser.print_help()
sys.exit(1)
return (options, args)
# ----------------------------------------------
def run(self, options, args):
''' use Runner lib to do SSH things '''
pattern = args[0]
sshpass = None
if options.ask_pass:
sshpass = getpass.getpass(prompt="SSH password: ")
runner = ansible.runner.Runner(
module_name=options.module_name,
module_path=options.module_path,
module_args=shlex.split(options.module_args),
remote_user=options.remote_user,
remote_pass=sshpass,
host_list=options.inventory,
timeout=options.timeout,
forks=options.forks,
background=options.seconds,
pattern=pattern,
verbose=True,
)
return (runner, runner.run())
# ----------------------------------------------
def get_polling_runner(self, old_runner, hosts, jid):
return ansible.runner.Runner(
module_name='async_status',
module_path=old_runner.module_path,
module_args=[ "jid=%s" % jid ],
remote_user=old_runner.remote_user,
remote_pass=old_runner.remote_pass,
host_list=hosts,
timeout=old_runner.timeout,
forks=old_runner.forks,
pattern='*',
verbose=True,
)
# ----------------------------------------------
def hosts_to_poll(self, results):
hosts = []
for (host, res) in results['contacted'].iteritems():
if res.get('started',False):
hosts.append(host)
return hosts
# ----------------------------------------------
def output(self, runner, results, options, args):
''' summarize results from Runner '''
if results is None:
exit("No hosts matched")
if options.tree:
prepare_writeable_dir(options.tree)
# BACKGROUND POLL LOGIC when -B and -P are specified
# FIXME: refactor
if options.seconds and options.poll_interval > 0:
poll_hosts = results['contacted'].keys()
if len(poll_hosts) == 0:
exit("no jobs were launched successfully")
ahost = poll_hosts[0]
jid = results['contacted'][ahost].get('ansible_job_id', None)
if jid is None:
exit("unexpected error: unable to determine jid")
clock = options.seconds
while (clock >= 0):
polling_runner = self.get_polling_runner(runner, poll_hosts, jid)
poll_results = polling_runner.run()
if poll_results is None:
break
for (host, host_result) in poll_results['contacted'].iteritems():
# override last result with current status result for report
results['contacted'][host] = host_result
print async_poll_status(jid, host, clock, host_result)
clock = clock - options.poll_interval
time.sleep(options.poll_interval)
poll_hosts = self.hosts_to_poll(poll_results)
for (host, host_result) in results['contacted'].iteritems():
if 'started' in host_result:
results['contacted'][host] = { 'failed' : 1, 'rc' : None, 'msg' : 'timed out' }
buf = ''
for hostname in contacted_hosts(results):
msg = host_report_msg(
hostname,
options.module_name,
contacted_host_result(results, hostname),
options.one_line
)
if options.tree:
write_tree_file(options.tree, hostname, bigjson(results))
buf += msg
if has_dark_hosts(results):
buf += dark_hosts_msg(results)
print buf
########################################################
if __name__ == '__main__':
cli = Cli()
(options, args) = cli.parse()
(runner, results) = cli.run(options, args)
cli.output(runner, results, options, args)