From 672794f586dd73b91100f6dba76f6dcb6e2c76c9 Mon Sep 17 00:00:00 2001 From: Stephen Fromm Date: Mon, 30 Apr 2012 14:27:57 -0700 Subject: [PATCH] Add ansible-pull A first stab at a pull-based model for ansible. This does two things: 1. Invoke the git module via Runner to set up a git repository on the localhost. It sets up Runner to use transport='local' and forces the inventory to just 'localhost'. 2. Run any playbooks provided. By default, this wants to run the playbook local.yml. This also sets transport='local' and sets the host_list to a list: localhost, fqdn, and hostname. The reason for setting the host_list and not using override_hosts is because there may be plays in the playbook that are not meant for a specific host. That is, if the git repository is for the entire site and not host-specific, you don't want to override hosts and apply all plays to any given host. This has the downside of potentially running a play three times if the play is defined for 'hosts: all'. --- bin/ansible-pull | 147 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100755 bin/ansible-pull diff --git a/bin/ansible-pull b/bin/ansible-pull new file mode 100755 index 0000000000..df7b5d17be --- /dev/null +++ b/bin/ansible-pull @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +# (c) 2012, Stephen Fromm +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +import os +import sys +import socket +import logging + +import ansible.playbook +import ansible.runner +import ansible.constants as C +from ansible import errors +from ansible import callbacks +from ansible import utils +from ansible import inventory + +DEFAULT_PLAYBOOK = 'local.yml' + +def main(args): + """ Set up and run a local playbook """ + usage = "%prog [options]" + parser = utils.base_parser(constants=C, usage=usage, + connect_opts=False, runas_opts=False) + parser.set_defaults(module_name='git', transport='local', + one_line=False, tree=None) + parser.add_option('-d', '--directory', dest='dest', default=None, + help='Directory to checkout git repository') + parser.add_option('-U', '--url', dest='url', default=None, + help='URL of git repository') + parser.add_option('-C', '--checkout', dest='checkout', default="HEAD", + help='Branch/Tag/Commit to checkout. Defaults to HEAD.') + parser.remove_option('-k') # Remove ssh password option + parser.remove_option('-K') # Remove sudo password option + parser.remove_option('-T') # Remove ssh timeout option + options, args = parser.parse_args(args) + + clirunner_cb = callbacks.CliRunnerCallbacks() + clirunner_cb.options = options + + # ---------------------------------------------- + # First git clone/pull + + git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout) + pattern = "localhost" + inventory_manager = inventory.Inventory([pattern]) + + """ + Ideally, changes should be reported via logging and not to STDOUT + """ + + runner = ansible.runner.Runner( + module_name=options.module_name, + module_args=git_opts, + module_path=options.module_path, + inventory=inventory_manager, + forks=options.forks, + pattern=pattern, + callbacks=clirunner_cb, + transport=options.transport, + debug=options.debug + ) + try: + runner.run() + except errors.AnsibleError, e: + print >>sys.stderr, "ERROR: %s" % e + return 1 + + # ---------------------------------------------- + # Second, run the playbook + """ + Change to the directory where the git checkout is located. + Insert 'local.yml' as the first playbook to be run. This + supports multiple playbooks being supplied on the CLI, similar + to ansible-playbook. This then loops on all the playbooks, + instantiates and runs a playbook. A couple things of note: + * The transport uses the default set above, local + * The host_list argument to Playbook is set to a list of + names. These are localhost, the fqdn, and hostname. + This last point is problematic because it will run a playbook + 3 times if the playbook is for 'all' hosts. We do not necessarily + want to override 'hosts' in the playbook because they may be generic + across the entire infrastructure -- not host specific. + + Finally, this should use the logging module in some manner and + not print data to STDOUT. + """ + + if os.path.exists("%s/%s" % (options.dest, DEFAULT_PLAYBOOK)): + args.insert(0, DEFAULT_PLAYBOOK) + os.chdir(options.dest) + hostname = socket.getfqdn() + stats = callbacks.AggregateStats() + playbook_cb = callbacks.PlaybookCallbacks() + pbrunner_cb = callbacks.PlaybookRunnerCallbacks(stats) + local_host = [pattern, hostname, hostname.split('.')[0]] + + for playbook in args: + pb = ansible.playbook.PlayBook( + playbook=playbook, + host_list=local_host, + module_path=options.module_path, + debug=options.debug, + runner_callbacks=pbrunner_cb, + callbacks=playbook_cb, + transport=options.transport, + stats=stats + ) + """ + This just takes the reporting from ansible-playbook. + Ideally, this should use logging to report success/failure/changes. + """ + try: + pb.run() + hosts = sorted(pb.stats.processed.keys()) + print "RECAP\n\n" + for h in hosts: + t = pb.stats.summarize(h) + print "%-30s : ok=%4s changed=%4s unreachable=%4s failed=%4s " % (h, + t['ok'], t['changed'], t['unreachable'], t['failures'] + ) + print "\n" + except errors.AnsibleError, e: + print >>sys.stderr, "ERROR: %s" % e + return 1 + + return 0 + +if __name__ == '__main__': + try: + sys.exit(main(sys.argv[1:])) + except errors.AnsibleError, e: + print >>sys.stderr, "ERROR: %s" % e + sys.exit(1)