ansible-pull 5.23 KB
Newer Older
Stephen Fromm committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/usr/bin/env python

# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
# ansible-pull is a script that runs ansible in local mode
# after checking out a playbooks directory from git.  There is an
# example playbook to bootstrap this script in the examples/ dir which
# installs ansible and sets it up to run on cron.

# usage:
#   ansible-pull -d /var/lib/ansible \
#                -U http://example.net/content.git [-C production] \
#                [path/playbook.yml]
#
# the -d and -U arguments are required; the -C argument is optional.
#
# ansible-pull accepts an optional argument to specify a playbook
# location underneath the workdir and then searches the git repo
# for playbooks in the following order, stopping at the first match:
#
# 1. $workdir/path/playbook.yml, if specified
# 2. $workdir/$hostname.yml
# 3. $workdir/local.yml
#
# the git repo must contain at least one of these playbooks.
Stephen Fromm committed
38 39

import os
40
import shutil
41
import subprocess
Stephen Fromm committed
42
import sys
43
import datetime
44
import socket
45
from optparse import OptionParser
Stephen Fromm committed
46 47

DEFAULT_PLAYBOOK = 'local.yml'
48 49
PLAYBOOK_ERRORS = { 1: 'File does not exist',
                    2: 'File is not readable' }
Stephen Fromm committed
50

51
def _run(cmd):
52
    print >>sys.stderr, "Running: '%s'" % cmd
53 54 55 56 57
    cmd = subprocess.Popen(cmd, shell=True,
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = cmd.communicate()
    print out
    if cmd.returncode != 0:
58
        print >>sys.stderr, err
59 60
    return cmd.returncode

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
def try_playbook(path):
    if not os.path.exists(path):
        return 1
    if not os.access(path, os.R_OK):
        return 2
    return 0

def select_playbook(path, args):
    playbook = None
    if len(args) > 0 and args[0] is not None:
        playbook = "%s/%s" % (path, args[0])
        rc = try_playbook(playbook)
        if rc != 0:
            print >>sys.stderr, "%s: %s" % (playbook, PLAYBOOK_ERRORS[rc])
            return None
        return playbook
    else:
        hostpb = "%s/%s.yml" % (path, socket.getfqdn())
        localpb = "%s/%s" % (path, DEFAULT_PLAYBOOK)
        errors = []
        for pb in [hostpb, localpb]:
            rc = try_playbook(pb)
            if rc == 0:
                playbook = pb
                break
            else:
                errors.append("%s: %s" % (pb, PLAYBOOK_ERRORS[rc]))
        if playbook is None:
            print >>sys.stderr, "\n".join(errors)
        return playbook

Stephen Fromm committed
92 93
def main(args):
    """ Set up and run a local playbook """
94
    usage = "%prog [options] [playbook.yml]"
95
    parser = OptionParser(usage=usage)
96 97
    parser.add_option('--purge', default=False, action='store_true',
                      help='Purge git checkout after playbook run')
Stephen Fromm committed
98
    parser.add_option('-d', '--directory', dest='dest', default=None,
99
                      help='Directory to clone git repository to')
100
    parser.add_option('-U', '--url', dest='url', default=None,
Stephen Fromm committed
101
                      help='URL of git repository')
102 103
    parser.add_option('-C', '--checkout', dest='checkout',
                      default="HEAD",
Stephen Fromm committed
104
                      help='Branch/Tag/Commit to checkout.  Defaults to HEAD.')
105 106
    parser.add_option('-i', '--inventory-file', dest='inventory',
                      help="specify inventory host file")
Stephen Fromm committed
107 108
    options, args = parser.parse_args(args)

109
    if not options.dest:
110 111
        parser.error("Missing required directory argument")
        return 1
112 113

    if not options.url:
114 115
        parser.error("URL for git repo not specified, use -h for help")
        return 1
116 117

    now = datetime.datetime.now()
118
    print >>sys.stderr, now.strftime("Starting ansible-pull at %F %T")
119

120
    inv_opts = 'localhost,'
121
    limit_opts = 'localhost:%s:127.0.0.1' % socket.getfqdn()
122
    git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout)
123 124 125
    cmd = 'ansible all -c local -i "%s" --limit "%s" -m git -a "%s"' % (
            inv_opts, limit_opts, git_opts
            )
126 127 128 129
    rc = _run(cmd)
    if rc != 0:
        return rc

130 131 132 133 134
    playbook = select_playbook(options.dest, args)

    if playbook is None:
        print >>sys.stderr, "Could not find a playbook to run."
        return 1
135

136
    cmd = 'ansible-playbook -c local --limit "%s" %s' % (limit_opts, playbook)
137 138
    if options.inventory:
        cmd += ' -i "%s"' % options.inventory
Stephen Fromm committed
139
    os.chdir(options.dest)
140
    rc = _run(cmd)
141 142 143 144 145 146 147 148

    if options.purge:
        os.chdir('/')
        try:
            shutil.rmtree(options.dest)
        except Exception, e:
            print >>sys.stderr, "Failed to remove %s: %s" % (options.dest, str(e))

149
    return rc
Stephen Fromm committed
150 151 152 153

if __name__ == '__main__':
    try:
        sys.exit(main(sys.argv[1:]))
154 155
    except KeyboardInterrupt, e:
        print >>sys.stderr, "Exit on user request.\n"
Stephen Fromm committed
156
        sys.exit(1)