diff --git a/bin/ansible b/bin/ansible index 4254b0b7fb..92316fcd67 100755 --- a/bin/ansible +++ b/bin/ansible @@ -78,6 +78,7 @@ class Cli(object): ) else: return ansible.playbook.PlayBook( + playbook=options.playbook, module_path=options.module_path, remote_user=options.remote_user, remote_pass=sshpass, diff --git a/examples/playbook.yml b/examples/playbook.yml index 7b5ec9c5c4..8a4cf19aef 100644 --- a/examples/playbook.yml +++ b/examples/playbook.yml @@ -1,12 +1,12 @@ -- pattern: '*.prod.example.com' +- pattern: '*' tasks: - do: - - update apache (note: service module TBD) + - update apache - command - [/usr/bin/yum, update, apache] onchange: - do: - - restart apache (note: service module TBD) + - restart apache - command - [/sbin/service, apache, restart] - do: diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index fa6c27731e..e8bb15d9c4 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -30,7 +30,13 @@ import yaml class PlayBook(object): ''' runs an ansible playbook, given as a datastructure - or YAML filename + or YAML filename. a playbook is a deployment, config + management, or automation based set of commands to + run in series. + + multiple patterns do not execute simultaneously, + but tasks in each pattern do execute in parallel + according to the number of forks requested. ''' def __init__(self, @@ -44,31 +50,116 @@ class PlayBook(object): verbose=False): # runner is reused between calls - - self.runner = ansible.runner.Runner( - host_list=host_list, - module_path=module_path, - forks=forks, - timeout=timeout, - remote_user=remote_user, - remote_pass=remote_pass, - verbose=verbose - ) + + self.host_list = host_list + self.module_path = module_path + self.forks = forks + self.timeout = timeout + self.remote_user = remote_user + self.remote_pass = remote_pass + self.verbose = verbose if type(playbook) == str: playbook = yaml.load(file(playbook).read()) - + self.playbook = playbook + def run(self): - pass + ''' run against all patterns in the playbook ''' + + for pattern in self.playbook: + self._run_pattern(pattern) + return "complete" + + def _get_task_runner(self, + pattern=None, + host_list=None, + module_name=None, + module_args=None): + + print "GET TASK RUNNER FOR HL=%s" % host_list + + ''' + return a runner suitable for running this task, using + preferences from the constructor + ''' + + if host_list is None: + host_list = self.host_list + + return ansible.runner.Runner( + pattern=pattern, + module_name=module_name, + module_args=module_args, + host_list=host_list, + forks=self.forks, + remote_user=self.remote_user, + remote_pass=self.remote_pass, + module_path=self.module_path, + timeout=self.timeout + ) + + def _run_task(self, pattern, task, host_list=None): + ''' + run a single task in the playbook and + recursively run any subtasks. + ''' + + if host_list is None: + host_list = self.host_list + + print "TASK=%s" % task + instructions = task['do'] + (comment, module_name, module_args) = instructions + print "running task: (%s) on hosts matching (%s)" % (comment, pattern) + runner = self._get_task_runner( + pattern=pattern, + module_name=module_name, + module_args=module_args + ) + results = runner.run() + print "RESULTS=%s" % results + + dark = results.get("dark", []) + contacted = results.get("contacted", []) + + # TODO: filter based on values that indicate + # they have changed events to emulate Puppet + # 'notify' behavior, super easy -- just + # a list comprehension -- but we need complaint + # modules first + + ok_hosts = contacted.keys() + + for host, msg in dark.items(): + print "contacting %s failed -- %s" % (host, msg) + + subtasks = task.get('onchange', []) + if len(subtasks) > 0: + print "the following hosts have registered change events" + print ok_hosts + for subtask in subtasks: + self._run_task(pattern, subtask, ok_hosts) + + # TODO: if a host fails in task 1, add it to an excludes + # list such that no other tasks in the list ever execute + # unlike Puppet, do not allow partial failure of the tree + # and continuing as far as possible. Fail fast. + + + def _run_pattern(self, pg): + ''' + run a list of tasks for a given pattern, in order + ''' + + pattern = pg['pattern'] + tasks = pg['tasks'] + print "PATTERN=%s" % pattern + print "TASKS=%s" % tasks + for task in tasks: + print "*** RUNNING A TASK (%s)***" % task + self._run_task(pattern, task) + -# r = Runner( -# host_list = DEFAULT_HOST_LIST, -# module_name='ping', -# module_args='', -# pattern='*', -# forks=3 -# ) -# print r.run()