from __future__ import with_statement import os import glob import sys import time import hashlib from fabric.api import local as fab_local, cd, sudo as fab_sudo, run as fab_run, hosts, roles, prompt, hide, show, settings, abort from fabric.operations import _shell_escape from fabric.state import commands from fabric.state import env from fabric.main import _internals from subprocess import PIPE, STDOUT from functools import wraps import getpass LOCAL_PACKAGES = ( '', ) PY_VERSION = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) def require_profile(func): @wraps(func) def _decorator(*args, **kwargs): if not getattr(env, 'profile_selected', False): print '\nRunning script on `local` environment, to specify a specific configuration, run `fab CONFIGURATION %s`\n' % func.__name__ local() if getattr(env, 'profile_hash', '') and not getattr(env, 'profile_hash_verified', False): profile_hash = getpass.getpass('Please enter the password to operate on this profile: ') if hashlib.md5(profile_hash).hexdigest() == getattr(env, 'profile_hash', ''): env.profile_hash_verified = True return func(*args, **kwargs) else: abort('Invalid password.') else: return func(*args, **kwargs) return _decorator def run(*args, **kwargs): if env.host == 'localhost': return fab_local(*args, **kwargs) else: return fab_run(*args, **kwargs) def sudo(command, *args, **kwargs): if env.host == 'localhost': user = kwargs.get('user', 'root') return fab_local('sudo -H -u %s /bin/bash -c "%s"' % (user, _shell_escape(command))) #return fab_local('/bin/bash -c "%s"' % _shell_escape(command)) else: return fab_sudo(command, *args, **kwargs) @require_profile def bootstrap(): """ Setup a localized virtual environment for python and install all of the required packages to get the site up and running. """ with cd(env.path): # Remove old virtual environment sudo("rm -Rf .ve~/", user=env.code_user) # Instatntiate new virtual environment sudo('virtualenv .ve~', user=env.code_user) # Install project specific fabric, pip & mercurial sudo('.ve~/bin/easy_install -q http://git.fabfile.org/cgit.cgi/fabric/snapshot/fabric-0.9b1.tar.gz pip==0.4 mercurial', user=env.code_user) # Activate new virtual environment sudo('sed \'s/(`basename \\\\"\\$VIRTUAL_ENV\\\\\"`)/(`basename \\\\`dirname \\\\"$VIRTUAL_ENV\\\\"\\\\``)/g\' .ve~/bin/activate > .ve~/bin/activate.tmp', user=env.code_user) sudo('mv .ve~/bin/activate.tmp .ve~/bin/activate', user=env.code_user) # PIP install requirements sudo("PIP_DOWNLOAD_CACHE=dist/.pip-cache/ .ve~/bin/pip install -I --source=.ve~/src/ --environment=.ve~/ -r REQUIREMENTS", user=env.code_user) # Cleanup pip install process sudo("rm -Rf build/", user=env.code_user) # Add local src folders to python path. sudo("echo '%s' >> .ve~/lib/%s/site-packages/easy-install.pth" % ('\n'.join([ os.path.join(env.path, 'src', x) for x in LOCAL_PACKAGES ]), PY_VERSION), user=env.code_user) # Apply patches. for patch in glob.glob(os.path.join(env.path, 'dist', 'patches', '*.patch')): dirname, filename = os.path.split(patch) application_name = filename.split('.patch')[0] application_path = os.path.join(env.path, '.ve~', 'src', application_name) if os.path.exists(application_path): print 'Patching %s...' % application_name sudo("cd %s; patch -p0 < %s" % (application_path, patch), user=env.code_user) # Cleanup and .pyc files that got generated so that we do not have any corrupt pyc files hanging out with the .ve~ path. sudo("find .ve~/ -type f -name '*.pyc' -print0 | xargs -0 rm -f", user=env.code_user) # Prepare the temporary virtualenv to take the place of the primary virtualenv by replacing the paths sudo("find .ve~/ -type f -print0 | xargs -0 sed -i 's/\.ve~/ve/g'", user=env.code_user) # Atomically move the new virtualenv into place, and move the old virtualenv into a backup location. sudo("rm -Rf .ve.old~/ 2> /dev/null; mv ve/ .ve.old~/ 2> /dev/null; mv .ve~/ ve/", user=env.code_user) @require_profile def deploy(revision=None): """ Deploy the latest stable code to a set of servers. """ if not hasattr(env, 'revision'): env.revision = None if env.revision is None and revision is not None: env.revision = revision with cd(env.path): sudo("git fetch", user=env.code_user) sudo("git fetch --tags", user=env.code_user) sudo("git reset --hard", user=env.code_user) with hide('stdout'): tags = sudo("git tag", user=env.code_user).split() initial = True while True: if (initial and env.revision is None) or not initial: env.revision = prompt("What branch would you like to deploy?") initial = False if env.revision in tags: break else: print "You must select a valid deployment tag.\n %s" % "\n ".join(tags) sudo("git checkout %s" % env.revision, user=env.code_user) sudo("find . -name '*.pyc' -type f | xargs rm -f") reboot() @require_profile def reboot(): """ Reboot the apache webserver gracefully. """ sudo("/sbin/service httpd graceful", user='root') @require_profile def bootstrap_on_demand(): from subprocess import Popen # Marker for stale bootstrap stale = False # Grab the current git revision f = open(os.path.join(env.path, '.git', 'HEAD'), 'r') rev = f.read().strip() f.close() # Get last bootstrap revision, if any. try: f = open(os.path.join(env.path, '.bootstrap'), 'r') last_rev = f.read() f.close() except IOError: last_rev = None if last_rev: p = Popen(['git', '--no-pager', 'diff', '--name-only', '%s..HEAD' % last_rev], stdout=PIPE, stderr=STDOUT) output = p.communicate()[0] if filter(lambda x: x == 'REQUIREMENTS', output.split()) or not os.path.exists('ve/'): stale = True else: stale = True if stale: print 'Virtual Environment is out of date.' bootstrap() else: print 'Virtual Environment is up-to-date. Skipping bootstrap.' # Write the current revision to the bootstrap file on success. f = open(os.path.join(env.path, '.bootstrap'), 'w') f.write(rev) f.close() # Profile section def local(): """ Local development server profile. """ env.host = 'localhost' env.path = os.path.abspath(os.path.dirname(__file__)) env.profile_selected = True env.code_user = env.user local.fab_profile = True def production(): """ Production server profile. """ env.hosts = ['ec2-server-1', 'ec2-server-2', 'ec2-server-3'] env.user = 'admin' env.path = '/home/project/root' env.password = 'password' env.code_user = 'apache' env.profile_selected = True env.profile_hash = '97e43572f170e47d0df46fad5a3fe7d9' production.fab_profile = True _internals.extend([run, sudo, require_profile, wraps])