diff --git a/bin/ansible b/bin/ansible index d6070c2c840..6425dcd7144 100755 --- a/bin/ansible +++ b/bin/ansible @@ -26,8 +26,8 @@ import json import os import ansible -DEFAULT_HOST_LIST = '~/.ansible_hosts' -DEFAULT_MODULE_PATH = '~/ansible' +DEFAULT_HOST_LIST = '/etc/ansible/hosts' +DEFAULT_MODULE_PATH = '/usr/share/ansible' DEFAULT_MODULE_NAME = 'ping' DEFAULT_PATTERN = '*' DEFAULT_FORKS = 3 @@ -54,7 +54,6 @@ class Cli(object): help="hostname pattern", default=DEFAULT_PATTERN) options, args = parser.parse_args() - host_list = self._host_list(options.host_list) # TODO: more shell like splitting on module_args would # be a good idea @@ -63,15 +62,12 @@ class Cli(object): module_name=options.module_name, module_path=options.module_path, module_args=options.module_args.split(' '), - host_list=host_list, + host_list=options.host_list, forks=options.forks, pattern=options.pattern, + verbose=False, ) - def _host_list(self, host_list): - host_list = os.path.expanduser(host_list) - return file(host_list).read().split("\n") - if __name__ == '__main__': result = Cli().runner().run() diff --git a/lib/ansible/__init__.py b/lib/ansible/__init__.py index a056727047b..4af0ba405aa 100755 --- a/lib/ansible/__init__.py +++ b/lib/ansible/__init__.py @@ -24,19 +24,18 @@ from multiprocessing import Process, Pipe from itertools import izip import os import json +import traceback # non-core import paramiko -# TODO -- library should have defaults, not just CLI -# update Runner constructor below to use - -DEFAULT_HOST_LIST = '~/.ansible_hosts' -DEFAULT_MODULE_PATH = '~/ansible' +DEFAULT_HOST_LIST = '/etc/ansible/hosts' +DEFAULT_MODULE_PATH = '/usr/share/ansible' DEFAULT_MODULE_NAME = 'ping' DEFAULT_PATTERN = '*' DEFAULT_FORKS = 3 DEFAULT_MODULE_ARGS = '' +DEFAULT_TIMEOUT = 60 class Pooler(object): @@ -59,20 +58,40 @@ class Pooler(object): class Runner(object): - def __init__(self, host_list=[], module_path=None, - module_name=None, module_args=[], - forks=3, timeout=60, pattern='*'): - - self.host_list = host_list + def __init__(self, + host_list=DEFAULT_HOST_LIST, + module_path=DEFAULT_MODULE_PATH, + module_name=DEFAULT_MODULE_NAME, + module_args=DEFAULT_MODULE_ARGS, + forks=DEFAULT_FORKS, + timeout=DEFAULT_TIMEOUT, + pattern=DEFAULT_PATTERN, + verbose=False): + + + ''' + Constructor. + ''' + + self.host_list = self._parse_hosts(host_list) self.module_path = module_path self.module_name = module_name self.forks = forks self.pattern = pattern self.module_args = module_args self.timeout = timeout - + self.verbose = verbose + + def _parse_hosts(self, host_list): + ''' parse the host inventory file if not sent as an array ''' + if type(host_list) != list: + host_list = os.path.expanduser(host_list) + return file(host_list).read().split("\n") + return host_list + def _matches(self, host_name): + ''' returns if a hostname is matched by the pattern ''' if host_name == '': return False if fnmatch.fnmatch(host_name, self.pattern): @@ -80,6 +99,7 @@ class Runner(object): return False def _connect(self, host): + ''' obtains a paramiko connection to the host ''' ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: @@ -87,20 +107,29 @@ class Runner(object): allow_agent=True, look_for_keys=True) return ssh except: + # TODO -- just convert traceback to string + # and return a seperate hash of failed hosts + if self.verbose: + traceback.print_exc() return None def _executor(self, host): + ''' callback executed in parallel for each host ''' # TODO: try/catch returning none + conn = self._connect(host) if not conn: return [ host, None ] + if self.module_name != "copy": + # transfer a module, set it executable, and run it outpath = self._copy_module(conn) self._exec_command(conn, "chmod +x %s" % outpath) cmd = self._command(outpath) result = self._exec_command(conn, cmd) result = json.loads(result) else: + # SFTP file copy module is not really a module ftp = conn.open_sftp() ftp.put(self.module_args[0], self.module_args[1]) ftp.close() @@ -109,23 +138,32 @@ class Runner(object): return [ host, result ] def _command(self, outpath): + ''' form up a command string ''' cmd = "%s %s" % (outpath, " ".join(self.module_args)) return cmd def _exec_command(self, conn, cmd): + ''' execute a command over SSH ''' stdin, stdout, stderr = conn.exec_command(cmd) results = stdout.read() return results def _copy_module(self, conn): - inpath = os.path.expanduser(os.path.join(self.module_path, self.module_name)) - outpath = os.path.join("/var/spool/", "ansible_%s" % self.module_name) - ftp = conn.open_sftp() - ftp.put(inpath, outpath) - ftp.close() - return outpath + ''' transfer a module over SFTP ''' + in_path = os.path.expanduser( + os.path.join(self.module_path, self.module_name) + ) + out_path = os.path.join( + "/var/spool/", + "ansible_%s" % self.module_name + ) + sftp = conn.open_sftp() + sftp.put(in_path, out_path) + sftp.close() + return out_path def run(self): + ''' xfer & run module on all matched hosts ''' hosts = [ h for h in self.host_list if self._matches(h) ] def executor(x): return self._executor(x) @@ -136,12 +174,10 @@ class Runner(object): if __name__ == '__main__': - - # TODO: if host list is string load from file + # test code... r = Runner( - host_list = [ '127.0.0.1' ], - module_path='~/ansible', + host_list = DEFAULT_HOST_LIST, module_name='ping', module_args='', pattern='*', diff --git a/setup.py b/setup.py index d5664ee55a9..25f72ee5fdd 100644 --- a/setup.py +++ b/setup.py @@ -14,10 +14,12 @@ setup(name='ansible', 'ansible', ], data_files=[ - ('/usr/share/ancible', 'library/ping'), - ('/usr/share/ancible', 'library/command'), - ('/usr/share/ancible', 'library/facter'), - ('/usr/share/ancible', 'library/copy'), + ('/usr/share/ansible', [ + 'library/ping', + 'library/command', + 'library/facter', + 'library/copy', + ]) ], scripts=[ 'bin/ansible',