Source

fabric-deploy-django / fabfile.py

'''
Original idea from:

http://gist.github.com/571155

Hacked it to basically be completely original.

'''
import os
from fabric.api import *


# globals
env.project_name = 'PROJECT_NAME'
env.run_south_migrate = True
env.copy_base_settings_local = False
env.num_releases = 7


# Environments
def _is_local():
    require('run_type', provided_by=[local, staging, prod])
    return env.run_type == 'local'


def _get_callable(default=run):
    'Return local or default (run) based on hosts variable'
    require('run_type', provided_by=[local, staging, prod])
    def _local(cmd, *args, **kwargs):
        return local(cmd)
    return _local if _is_local() else default


def _get_cmds():
    'Return run, cd and sudo commands.'
    return (
        _get_callable(),
        _get_callable(default=sudo),
    )


def localdev():
    'Use the local machine'
    env.hosts = ['localhost']
    env.path = '/path/to/environments/PROJECT_NAME'
    env.user = 'username'
    env.run_type = 'local'
    env.repo_path = '/path/to/local/repository'
    env.repo_rev = 'tip'
    env.copy_base_settings_local = True


def staging():
    'Use the staging environment'
    pass


def prod():
    'Use the production environment'
    env.hosts = ['yourproject.yourdomain.com']
    env.path = '/path/to/environments/PROJECT_NAME'
    env.user = 'username'
    env.run_type = 'prod'
    env.repo_path = 'http://bitbucket.org/someuser/yourproject'
    env.repo_rev = 'tip'


def set_repo_revision(rev='tip'):
    env.repo_rev = rev


def set_num_releases(amount=7):
    env.num_releases = amount


# Tasks
def test():
    'Run the test suite and bail out if it fails'
    _run, _sudo = _get_cmds()

    with cd(env.project_name):
        _run('python manage.py test')


def setup():
    '''
    Setup a fresh virtualenv as well as a few useful directories, then run
    a full deployment
    '''
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    _run, _sudo = _get_cmds()
    
    import time
    env.release = time.strftime('%Y%m%d%H%M%S')

    lpath = '%(path)s/releases/%(release)s' % env
    _run('mkdir -p %s' % lpath)
    with cd(lpath):
        _run('virtualenv --no-site-packages .')
        _run('mkdir -p shared packages')
    deploy()


def soft_update():
    'Just update codebase. Do not do a full deploy'
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    
    checkout_code_repo(update=True)
    touch_wsgi_handler()


def deploy():
    '''
    Deploy the latest version of the site to the servers, install any
    required third party modules, install the virtual host and 
    then restart the webserver
    '''
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')

    checkout_code_repo()
    install_requirements()
    migrate()
    install_site()
    symlink_current_release()
    restart_webserver()
    remove_oldest_release()


def show_versions():
    'List all deployed versions'
    require('path')
    _run, _sudo = _get_cmds()

    with cd('%(path)s/releases' % env):
        print _run('ls -xt')


def rollback_version(version):
    'Specify a specific version to be made live'
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    _run, _sudo = _get_cmds()
    
    env.version = version
    with cd('%(path)s' % env):
        _run('ln -nfs releases/%(version)s current' % env)
    restart_webserver()


def rollback():
    'Simple GENERIC rollback. Symlink to the second most recent release'
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    _run, _sudo = _get_cmds()

    releases = _get_releases_list()
    release = releases[-2]
    _run('ln -nfs releases/%s current' % release)
    restart_webserver()


def fail_cleanup():
    '''
    If your VERY LAST deploy failed, call this to clean up the
    mess left behind. It will only remove the most recent 
    release directory. BE CAREFUL USING THIS.
    '''
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    _run, _sudo = _get_cmds()

    releases = _get_releases_list()
    release = releases[-1]
    with cd('%(path)s/releases' % env):
        _run('rm -rf %s' % release)


# Helpers. These are called by other functions rather than directly
def _get_releases_list():
    'Returns sorted list of all current releases'
    require('hosts', provided_by=[localdev, staging, prod])
    require('path')
    _run, _sudo = _get_cmds()

    with cd('%(path)s/releases' % env):
        releases = _run('ls -xt')
        releases = [x.replace('/', '') for x in releases.split()]
    return sorted(releases)


def checkout_code_repo(update=False):
    'Create an archive from the current Git master branch and upload it'
    require('repo_path', provided_by=[localdev, staging, prod])
    require('repo_rev', provided_by=[localdev, staging, prod])
    _run, _sudo = _get_cmds()

    if update:
        lpath = '%(path)s/current/%(project_name)s' % env
        with cd(lpath):
            _run('hg pull -r %(repo_rev)s -u' % env)
    else:
        require('release', provided_by=[deploy, setup])
        lpath = '%(path)s/releases/%(release)s' % env
        with cd(lpath):
            cmd = 'hg clone -r '
            cmd += '%(repo_rev)s %(repo_path)s %(project_name)s' % env
            _run(cmd)

    if env.copy_base_settings_local:
        lpath = os.path.join(lpath, '%(project_name)s/%(project_name)s' % env)
        with cd(lpath):
            with settings(warn_only=True):
                _run('cp base_settings_local.py settings_local.py')


def install_site():
    'Add the virtualhost file to apache'
    require('release', provided_by=[deploy, setup])
    _run, _sudo = _get_cmds()

    if not _is_local():
        with cd('%(path)s/releases/%(release)s' % env):
            cmd = 'cp %(project_name)s/apache/%(project_name)s.conf ' % env + \
                  '/etc/apache2/conf.d/'
            _sudo(cmd)


def install_requirements():
    'Install the required packages from the requirements file using pip'
    require('release', provided_by=[deploy, setup])
    _run, _sudo = _get_cmds()

    with cd('%(path)s/releases/%(release)s' % env):
        cmd = './bin/pip install -E . '
        cmd += '-r ./%(project_name)s/requirements.txt' % env
        _run(cmd)


def symlink_current_release():
    'Symlink our current release'
    require('release', provided_by=[deploy, setup])
    _run, _sudo = _get_cmds()

    with cd('%(path)s' % env):
        _run('ln -nfs releases/%(release)s current' % env)


def migrate():
    'Update the database'
    require('project_name')
    require('release', provided_by=[deploy, setup])
    _run, _sudo = _get_cmds()

    p = '%(path)s/releases/%(release)s/%(project_name)s/%(project_name)s' % env
    with cd(p):
        _run('../../bin/python manage.py syncdb --noinput')
        if env.run_south_migrate:
            _run('../../bin/python manage.py migrate')


def restart_webserver():
    'Restart the web server'
    _run, _sudo = _get_cmds()

    if not _is_local():
        _sudo('service apache2 restart')


def touch_wsgi_handler():
    'Touch the mod_wsgi wsgi_handler.py file'
    require('project_name')
    require('hosts', provided_by=[localdev, staging, prod])
    _run, _sudo = _get_cmds()

    with cd('%(path)s/current/%(project_name)s/apache' % env):
        _run('touch wsgi_handler.py')


def remove_oldest_release():
    '''
    Remove oldest releases past the ammount passed 
    in with the "allow" variable. Default 7.
    '''
    require('hosts', provided_by=[localdev, staging, prod])
    require('num_releases')
    require('path')
    _run, _sudo = _get_cmds()

    releases = _get_releases_list()
    with cd('%(path)s/releases' % env):
        while len(releases) > env.num_releases:
            release = releases.pop(0)
            _run('rm -rf %s' % release)