# -*- coding: utf-8 -*- __author__ = 'heddevanderheide' import datetime import os, sys from fabric.api import * from fabric.context_managers import lcd # todo's: # # - implement pip wheel (no need for pypi connections) # - log level configuration (pip --quiet etc) # - rolling back (reverse symlinks, remove release dir) # - local build dir is useless, should all move to /tmp # Main Configuration env.hosts = ['www.example.com'] env.user = 'johndoe' env.key_filename = '/Users/johndoe/.ssh/id_rsa.pub' # Stage Configuration class Test(object): """ [items] mappings of django settings that should be parsed into the local_settings.py (or params.py), the keys should be prefixed 'settings_' [stage_root] the absolute path to your remote stage root [release_version] the branch or tag you wish to deploy [timestamp_format] the way release directories should look """ items = dict(settings_DEBUG=True, settings_ENV='test').items() stage_root = '/home/johndoe/example.com/test.example.com' project_name = 'example' release_version = 'develop' timestamp_format = '%Y%m%d_%H%M%S' hosts = env.hosts user = env.user password = env.password class Production(Test): items = dict(settings_DEBUG=False, settings_ENV='production').items() stage_root = '/home/johndoe/example.com/example.com' release_version = 'develop' # Setup the Configuration Dict ENV = { 'test': Test(), 'production': Production() } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Tasks # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @task def deploy(env='test'): try: env = ENV.get(env) remote_uname() release = get_timestamp(env) create_build_directory(env, release) create_deploy_directory(env) create_timestamp_directory(env, release) checkout_version(env, release) pack_code(env, release) upload_code(env, release) unpack_tarball(env, release) create_virtualenv(env, release) pip_install_requirements(env, release) django_set_envs_for_release(env, release) with cd('{stage_root}/RELEASES/{release}/src'.format(stage_root=env.stage_root, release=release)): django_sync_migrate(env, release) django_compilemessages(env, release) django_collectstatic(env, release) switch_symlinks(env, release) except Exception, e: print 'ERROR:', e print 'INFO: rolling back...' finally: clean(env) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Functions # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def remote_uname(): # remote connection indicator run('uname -a') def create_build_directory(env, release): # build dir local( 'mkdir -p build/{project_name}/{release}'.format( project_name=env.project_name, release=release ) ) def get_timestamp(env): # returns release timestamp return datetime.datetime.now().strftime( '{timestamp_format}'.format(timestamp_format=env.timestamp_format) ) def create_deploy_directory(env): # creates the remote release dir if it # doesn't already exist run( 'mkdir -p {stage_root}/RELEASES'.format( stage_root=env.stage_root ) ) def checkout_version(env, release): # checkout the version we want to deploy local( 'git clone . /tmp/{release}'.format( release=release ) ) with lcd('/tmp/{release}'.format(release=release)): local( 'git checkout {version}'.format( version=env.release_version ) ) def pack_code(env, release): # tarball the version designated for deploy local('rm -rf /tmp/{release}/.git'.format(release=release)) local( 'tar czf build/{project_name}/{version}.tar.gz /tmp/{release}'.format( project_name=env.project_name, version=env.release_version, release=release ) ) def upload_code(env, release): # upload tarball to the remote local('scp build/{project_name}/{release}.tar.gz {user}@{host}:{remote_path}'.format( project_name=env.project_name, host=env.hosts[0], user=env.user, release=env.release_version, remote_path='{stage_root}'.format( stage_root=env.stage_root, release=env.release_version ) )) def create_timestamp_directory(env, timestamp): # create remote timestamp directory for deploy run( 'mkdir -p {stage_root}/RELEASES/{timestamp}'.format( stage_root=env.stage_root, timestamp=timestamp ) ) def unpack_tarball(env, release): # unpacks tar release into remote release dir run( 'tar xvf {stage_root}/{release_version}.tar.gz -C {stage_root}/RELEASES/{release} --strip-components=2'.format( stage_root=env.stage_root, release_version=env.release_version, release=release, project_name=env.project_name ) ) def create_virtualenv(env, release): # creates a virtualenv inside the release dir run( 'virtualenv {stage_root}/RELEASES/{release}/venv'.format( stage_root=env.stage_root, release=release ) ) def pip_install_requirements(env, release): # install the requirements.txt using the venv run( '{stage_root}/RELEASES/{release}/venv/bin/pip install -r {stage_root}/RELEASES/{release}/requirements.txt --quiet'.format( stage_root=env.stage_root, release=release ) ) def django_set_envs_for_release(env, release): # setup configured environment variables # local_settings.py / params.py path = 'build/{project_name}/{release}'.format( project_name=env.project_name, release=release ) local('touch {path}/local_settings.py'.format(path=path)) f = open('{path}/local_settings.py'.format(path=path), 'w+') string = "\n".join( [ "{key} = {value}".format(key=k[9:], value=False if v == '' else repr(v)) for k, v in env.items if k.startswith('settings_') ] ) f.write(string) f.close() local('scp {path}/local_settings.py {user}@{host}:{remote_path}'.format( path=path, project_name=env.project_name, host=env.hosts[0], user=env.user, release=env.release_version, remote_path='{stage_root}/RELEASES/{release}/src/main/'.format( stage_root=env.stage_root, release=release ) )) def django_collectstatic(env, release): run( '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py collectstatic --noinput'.format( stage_root=env.stage_root, release=release ) ) def django_compilemessages(env, release): run( '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py compilemessages'.format( stage_root=env.stage_root, release=release ) ) def django_sync_migrate(env, release): run( '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py syncdb --migrate'.format( stage_root=env.stage_root, release=release ) ) def switch_symlinks(env, release): symlink_wsgi = '{stage_root}/RELEASES/{release}/src/wsgi.py'.format( stage_root=env.stage_root, release=release ) symlink_current = '{stage_root}/RELEASES/{release}'.format( stage_root=env.stage_root, release=release ) run('touch {stage_root}/RELEASES/{release}/src/VERSION'.format(stage_root=env.stage_root, release=release)) run( 'echo "{release_version}" > {stage_root}/RELEASES/{release}/src/VERSION'.format( release_version=env.release_version, stage_root=env.stage_root, release=release, project_name=env.project_name ) ) run( 'ln -fs {symlink} {stage_root}/wsgi.py'.format( stage_root=env.stage_root, release=release, symlink=symlink_wsgi ) ) run( 'ln -snf {symlink} {stage_root}/current'.format( stage_root=env.stage_root, release=release, symlink=symlink_current ) ) def clean(env): run( 'ls -1dt {stage_root}/RELEASES/* | tail -n +6 | xargs rm -rf'.format( stage_root=env.stage_root ) )