From 79fdc1b6f4f43d9a014ecf56821a37a5fa7b098a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 25 Feb 2012 14:42:41 -0500 Subject: [PATCH] Notifable handlers only run when something is changed. Awesome. --- TODO.md | 36 ------------------------- examples/playbook.yml | 17 ++++++------ lib/ansible/playbook.py | 58 ++++++++++++++++++++++++++--------------- 3 files changed, 46 insertions(+), 65 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 1b1c7b2fe2f..00000000000 --- a/TODO.md +++ /dev/null @@ -1,36 +0,0 @@ -TODO list and plans -=================== - -Playbook TODO: - - * error codes and failure summaries - * handle 'changed' attributes - * fail nodes on errors, i.e. remove from host list, rather than continuing to pound them - * further improve output - * more conditional capability (if statement) (?) - * very good logging - -Command module: - * magic to pull async & timeout options off of the command line and not feed them - to the app we're executing - -General: - - * better logging - * async options - * modules for users, groups, and files, using puppet style ensure mechanics - * think about how to build idempotency (aka Puppet-style 'creates') around command module? - -Bonus utilities: - - * ansible-inventory - gathering fact/hw info, storing in git, adding RSS - * ansible-slurp - recursively rsync file trees for each host - * maybe it's own fact engine, not required, that also feeds from facter - -Not so interested really, but maybe: - - * list available modules from command line - * add/remove/list hosts from the command line - * filter exclusion (run this only if fact is true/false) - -- should be doable with playbooks (i.e. not neccessary) - diff --git a/examples/playbook.yml b/examples/playbook.yml index d79e4935616..5cc6cdf44d4 100644 --- a/examples/playbook.yml +++ b/examples/playbook.yml @@ -6,17 +6,18 @@ - do: - copy a file - copy /srv/a /srv/b + notify: + - restart apache - do: - template from local file template.j2 to remote location /srv/file.out - template /srv/template.j2 /srv/file.out - - do: - - update apache - - command /usr/bin/yum update apache - onchange: + notify: + - restart apache + - quack like a duck + handlers: - do: - restart apache - - command /sbin/service apache restart + - command /sbin/service httpd restart - do: - - run bin false - - command /bin/false - + - quack like a duck + - command /bin/true diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 769c315864f..258e59d7eef 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -101,7 +101,7 @@ class PlayBook(object): timeout=self.timeout ) - def _run_task(self, pattern, task, host_list=None, conditional=False): + def _run_task(self, pattern=None, task=None, host_list=None, handlers=None, conditional=False): ''' run a single task in the playbook and recursively run any subtasks. @@ -118,7 +118,7 @@ class PlayBook(object): namestr = "%s/%s" % (pattern, comment) if conditional: - namestr = "subset/%s" % namestr + namestr = "notified/%s" % namestr print "TASK [%s]" % namestr runner = self._get_task_runner( @@ -130,15 +130,7 @@ class PlayBook(object): results = runner.run() 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(): @@ -152,27 +144,51 @@ class PlayBook(object): print "FAIL: [%s/%s]" % (host, comment, results) - subtasks = task.get('onchange', []) + # flag which notify handlers need to be run + subtasks = task.get('notify', []) if len(subtasks) > 0: - for subtask in subtasks: - self._run_task(pattern, subtask, ok_hosts, conditional=True) + for host, results in contacted.items(): + if results.get('changed', False): + for subtask in subtasks: + self._flag_handler(handlers, subtask, host) - # 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. + # TODO: if a host fails in any task, remove it from + # the host list immediately + def _flag_handler(self, handlers, match_name, host): + ''' + if a task has any notify elements, flag handlers for run + at end of execution cycle for hosts that have indicated + changes have been made + ''' + for x in handlers: + attribs = x["do"] + name = attribs[0] + if match_name == name: + if not x.has_key("run"): + x['run'] = [] + x['run'].append(host) def _run_pattern(self, pg): ''' run a list of tasks for a given pattern, in order ''' - pattern = pg['pattern'] - tasks = pg['tasks'] - for task in tasks: - self._run_task(pattern, task) + pattern = pg['pattern'] + tasks = pg['tasks'] + handlers = pg['handlers'] + for task in tasks: + self._run_task(pattern=pattern, task=task, handlers=handlers) + for task in handlers: + if type(task.get("run", None)) == list: + self._run_task( + pattern=pattern, + task=task, + handlers=handlers, + host_list=task.get('run',[]), + conditional=True + )