Browse files


  • Loading branch information...
mpdehaan committed Feb 23, 2012
0 parents commit f31421576b00f0b167cdbe61217c31c21a41ac02
Showing with 253 additions and 0 deletions.
  1. +88 −0
  2. +165 −0 lib/ansible/
@@ -0,0 +1,88 @@
Ansible is a extra-simple Python API for doing 'remote things' over SSH.
As Func, which I co-wrote, aspired to avoid using SSH and have it's own daemon infrastructure, Ansible aspires to be quite different and more minimal, but still able to grow more modularly over time.
* Dead simple setup
* No server or client daemons, uses existing SSHd
* Only SSH keys are allowed for authentication
* usage of ssh-agent is more or less required
* plugins can be written in ANY language
* as with Func, API usage is an equal citizen to CLI usage
* python 2.6 -- or a backport of the multiprocessing module
* paramiko
Inventory file
The default inventory file (-H) is ~/.ansible_hosts and is a list
of all hostnames to target with ansible, one per line.
This list is further filtered by the pattern wildcard (-P) to target
specific hosts.
Comamnd line usage example
Run a module by name with arguments
ansible -p "*" -m modName -a "arg1 arg2"
API Example
The API is simple and returns basic datastructures.
import ansible
runner = ansible.Runner(command='inventory', host_list=['', '...'])
data =
'' : [ 'any kind of datastructure is returnable' ],
'' : None, # failed to connect,
Additional options to runner include the number of forks, hostname
exclusion pattern, library path, and so on.
Specify the number of forks to use, to run things in greater parallelism.
ansible -f 10 "*" -m modName -a "arg1 arg2"
Bundled Modules
See the example library for modules, they can be written in any language
and simply return JSON to stdout. The path to your ansible library is
specified with the "-L" flag should you wish to use a different location
than "~/ansible".
Features not supported from Func (by design)
* Delegation for treeish topologies
* Asynchronous modes for long running tasks -- background tasks on your own
Future plans
* Dead-simple declarative configuration management & facts engine, with
probes implementable in any language.
* Michael DeHaan <> |
@@ -0,0 +1,165 @@
# core
from optparse import OptionParser
import fnmatch
from multiprocessing import Process, Pipe
from itertools import izip
import os
import json
# non-core
import paramiko
DEFAULT_HOST_LIST = '~/.ansible_hosts'
class Pooler(object):
# credit:
def spawn(cls, f):
def fun(pipe,x):
return fun
def parmap(cls, f, X):
pipe=[Pipe() for x in X]
proc=[Process(target=cls.spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]
[p.start() for p in proc]
[p.join() for p in proc]
return [p.recv() for (p,c) in pipe]
class Cli(object):
def __init__(self):
def runner(self):
parser = OptionParser()
parser.add_option("-H", "--host-list", dest="host_list",
help="path to hosts list", default=DEFAULT_HOST_LIST)
parser.add_option("-L", "--library", dest="module_path",
help="path to module library", default=DEFAULT_MODULE_PATH)
parser.add_option("-F", "--forks", dest="forks",
help="level of parallelism", default=DEFAULT_FORKS)
parser.add_option("-n", "--name", dest="module_name",
help="module name to execute", default=DEFAULT_MODULE_NAME)
parser.add_option("-a", "--args", dest="module_args",
help="module arguments", default=DEFAULT_MODULE_ARGS)
parser.add_option("-p", "--pattern", dest="pattern",
help="hostname pattern", default=DEFAULT_PATTERN)
options, args = parser.parse_args()
host_list = self._host_list(options.host_list)
return Runner(
def _host_list(self, host_list):
host_list = os.path.expanduser(host_list)
return file(host_list).read().split("\n")
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
self.module_path = module_path
self.module_name = module_name
self.forks = forks
self.pattern = pattern
self.module_args = module_args
self.timeout = timeout
def _matches(self, host_name):
if host_name == '':
return False
if fnmatch.fnmatch(host_name, self.pattern):
return True
return False
def _connect(self, host):
ssh = paramiko.SSHClient()
ssh.connect(host, username='root',
allow_agent=True, look_for_keys=True)
return ssh
return None
def _executor(self, host):
# TODO: try/catch returning none
conn = self._connect(host)
if not conn:
return [ host, None ]
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)
return [ host, result ]
def _command(self, outpath):
cmd = "%s %s" % (outpath, self.module_args)
return cmd
def _exec_command(self, conn, cmd):
stdin, stdout, stderr = conn.exec_command(cmd)
results =
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)
return outpath
def run(self):
hosts = [ h for h in self.host_list if self._matches(h) ]
def executor(x):
return self._executor(x)
results = Pooler.parmap(executor, hosts)
by_host = dict(results)
return by_host
if __name__ == '__main__':
# comamnd line usage example:
result = Cli().runner().run()
print json.dumps(result, sort_keys=True, indent=4)
# API usage example:
#r = Runner(
# host_list = [ '' ],
# module_path='~/.ansible',
# module_name='ping',
# module_args='',
# pattern='*',
# forks=3

0 comments on commit f314215

Please sign in to comment.