diff --git a/README.md b/README.md index b88d6e88e5..1b6e919e23 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Requirements For the server the tool is running from, *only*: * python 2.6 -- or the 2.4/2.5 backport of the multiprocessing module + * PyYAML (if using playbooks) * paramiko Inventory file @@ -145,6 +146,15 @@ Modules include: * facter - retrieves facts about the host OS * copy - add files to remote systems +Playbooks +========= + +Playbooks are loosely equivalent to recipes or manifests in most configuration +management or deployment tools and describe a set of operations to run on +a set of hosts. Some tasks can choose to only fire when certain +conditions are true, and if a task in a chain fails the dependent tasks +will not proceed. Playbooks are described in (YAML)[http://yaml.org] format. + Future plans ============ diff --git a/bin/ansible b/bin/ansible index 690fcb6dea..94ff3921a5 100755 --- a/bin/ansible +++ b/bin/ansible @@ -26,14 +26,8 @@ import json import os import getpass import 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_REMOTE_USER = 'root' +import ansible.playbook +import ansible.constants as C class Cli(object): @@ -45,19 +39,21 @@ class Cli(object): parser.add_option("-P", "--askpass", default=False, action="store_true", help="ask the user to input the ssh password for connecting") parser.add_option("-H", "--host-list", dest="host_list", - help="path to hosts list", default=DEFAULT_HOST_LIST) + help="path to hosts list", default=C.DEFAULT_HOST_LIST) parser.add_option("-L", "--library", dest="module_path", - help="path to module library", default=DEFAULT_MODULE_PATH) + help="path to module library", default=C.DEFAULT_MODULE_PATH) parser.add_option("-f", "--forks", dest="forks", - help="level of parallelism", default=DEFAULT_FORKS) + help="level of parallelism", default=C.DEFAULT_FORKS) parser.add_option("-n", "--name", dest="module_name", - help="module name to execute", default=DEFAULT_MODULE_NAME) + help="module name to execute", default=C.DEFAULT_MODULE_NAME) parser.add_option("-a", "--args", dest="module_args", - help="module arguments", default=DEFAULT_MODULE_ARGS) + help="module arguments", default=C.DEFAULT_MODULE_ARGS) parser.add_option("-p", "--pattern", dest="pattern", - help="hostname pattern", default=DEFAULT_PATTERN) + help="hostname pattern", default=C.DEFAULT_PATTERN) parser.add_option("-u", "--remote-user", dest="remote_user", - help="remote username", default=DEFAULT_REMOTE_USER) + help="remote username", default=C.DEFAULT_REMOTE_USER) + parser.add_option("-r", "--run-playbook", dest="playbook", + help="playbook file, instead of -n and -a", default=None) options, args = parser.parse_args() @@ -68,17 +64,27 @@ class Cli(object): if options.askpass: sshpass = getpass.getpass(prompt="SSH password: ") - return ansible.Runner( - module_name=options.module_name, - module_path=options.module_path, - module_args=options.module_args.split(' '), - remote_user=options.remote_user, - remote_pass=sshpass, - host_list=options.host_list, - forks=options.forks, - pattern=options.pattern, - verbose=False, - ) + if options.playbook is None: + return ansible.Runner( + module_name=options.module_name, + module_path=options.module_path, + module_args=options.module_args.split(' '), + remote_user=options.remote_user, + remote_pass=sshpass, + host_list=options.host_list, + forks=options.forks, + pattern=options.pattern, + verbose=False, + ) + else: + return ansible.playbook.PlayBook( + module_path=options.module_path, + remote_user=options.remote_user, + remote_pass=sshpass, + host_list=options.host_list, + forks=options.forks, + verbose=False, + ) if __name__ == '__main__': diff --git a/lib/ansible/__init__.py b/lib/ansible/__init__.py index f58797061c..2517e51455 100755 --- a/lib/ansible/__init__.py +++ b/lib/ansible/__init__.py @@ -28,15 +28,7 @@ import traceback # non-core import paramiko -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 -DEFAULT_REMOTE_USER = 'root' -DEFAULT_REMOTE_PASS = None +import constants as C def _executor_hook(x): ''' callback used by multiprocessing pool ''' @@ -46,15 +38,15 @@ def _executor_hook(x): class Runner(object): 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, - remote_user=DEFAULT_REMOTE_USER, - remote_pass=DEFAULT_REMOTE_PASS, + host_list=C.DEFAULT_HOST_LIST, + module_path=C.DEFAULT_MODULE_PATH, + module_name=C.DEFAULT_MODULE_NAME, + module_args=C.DEFAULT_MODULE_ARGS, + forks=C.DEFAULT_FORKS, + timeout=C.DEFAULT_TIMEOUT, + pattern=C.DEFAULT_PATTERN, + remote_user=C.DEFAULT_REMOTE_USER, + remote_pass=C.DEFAULT_REMOTE_PASS, verbose=False): diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py new file mode 100644 index 0000000000..15ad18de6b --- /dev/null +++ b/lib/ansible/constants.py @@ -0,0 +1,10 @@ +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 +DEFAULT_REMOTE_USER = 'root' +DEFAULT_REMOTE_PASS = None + diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py new file mode 100755 index 0000000000..d88946d4e1 --- /dev/null +++ b/lib/ansible/playbook.py @@ -0,0 +1,74 @@ +# Copyright (c) 2012 Michael DeHaan +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import ansible +import ansible.constants as C +import json +import yaml + +# TODO: make a constants file rather than +# duplicating these + +class PlayBook(object): + ''' + runs an ansible playbook, given as a datastructure + or YAML filename + ''' + + def __init__(self, + 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, + verbose=False): + + # runner is reused between calls + + self.runner = ansible.Runner( + host_list=host_list, + module_path=module_path, + forks=forks, + timeout=timeout, + remote_user=remote_user, + remote_pass=remote_pass, + verbose=verbose + ) + + if type(playbook) == str: + playbook = yaml.load(file(playbook).read()) + + def run(self): + pass + +# r = Runner( +# host_list = DEFAULT_HOST_LIST, +# module_name='ping', +# module_args='', +# pattern='*', +# forks=3 +# ) +# print r.run() + + +