Snippets

Benjamin Pollack My old bitquabit deployment system

Created by Benjamin Pollack
import errno
import hashlib
import posixpath

from os import makedirs, path
from datetime import datetime
from StringIO import StringIO

from fabric.api import cd, env, lcd, local, put, sudo, task
from fabric.contrib.files import exists as file_exists
from jinja2 import FileSystemLoader, Environment

RESOURCES_ROOT = path.abspath(path.dirname(__file__))
ENV = Environment(loader=FileSystemLoader(path.join(RESOURCES_ROOT, 'templates')))
FLASK_ROOT = '/var/www/flask'

# This was a local cache of the build; it's not used terribly well in this particular Fabfile
DEPLOYER_BUILDS = path.abspath(path.join(RESOURCES_ROOT, 'deploycache'))
try:
    makedirs(DEPLOYER_BUILDS)
except OSError as e:
    if e.errno != errno.EEXIST:
        raise

env.roledefs = {
    'production': {
        'hosts': ['somehost.example.com', 'maybe.another.example.com', 'a.third.example.com'],
        'user': 'youruser'
    }
}


@task(alias='blag')
def deploy_blog(environment='production', rev='@'):
    # This is a bit racy, but not in practice, since I'm the only one who
    # deploys my blog, and there's only one of me
    work_dir = 'blog-{}'.format(datetime.now().strftime('%Y%m%dT%H%M%S'))
    work_repo = '{}.hg'.format(work_dir)

    with lcd(DEPLOYER_BUILDS):
        local('hg clone -U ssh://bb/bpollack/blag {}'.format(work_repo))
        local('hg archive -t files -R {0} -r {1} {2}'.format(work_repo, rev, work_dir))

    # Build everything locally into a package and upload the package
    # I could also use devpi here, but I don't bother
    with lcd(path.join(DEPLOYER_BUILDS, work_dir)):
        version = local('python setup.py --version', capture=True)
        sdist_name = 'blag-%s.tar.gz' % version
        local('python setup.py preflight')
        local('python setup.py sdist')
        sdist_path = path.join(DEPLOYER_BUILDS, work_dir, 'dist', sdist_name)
        put(sdist_path, FLASK_ROOT, use_sudo=True)

    venv_name, venv_path = ensure_virtualenv(path.join(DEPLOYER_BUILDS, work_dir), work_dir)
    config_path = '/var/www/flask/{}.cfg'.format(work_dir)
    sudo('%(pip)s install --process-dependency-links -U %(package)s' % {
        'pip': '{}/bin/pip'.format(venv_path),
        'package': posixpath.join(FLASK_ROOT, sdist_name),
    })
    ini = StringIO(ENV.get_template('blog/blog.ini').render(version=version, venv=venv_name))
    service = StringIO(ENV.get_template('blog/bitquabit-blog.service').render(config_path=config_path))

    with cd(FLASK_ROOT):
        put(ini, posixpath.join(FLASK_ROOT, 'blog.ini'), use_sudo=True)
        sudo('rm blog-static', warn_only=True)
        static_dir = '{}/lib/python2.7/site-packages/blog/static'.format(venv_path)
        sudo('ln -s {0} {1}'.format(static_dir, 'blog-static'))

    with lcd(DEPLOYER_BUILDS):
        local('rm -rf {0} {1}'.format(work_dir, work_repo))

    put(service, '/etc/systemd/system/bitquabit-blog.service', use_sudo=True)
    put(path.join(RESOURCES_ROOT, 'templates', 'blog', 'blog.cfg'), '/var/www/flask/{}.cfg'.format(work_dir), use_sudo=True)
    sudo('systemctl daemon-reload')
    sudo('systemctl restart bitquabit-blog')


def ensure_virtualenv(root, prefix='generic'):
    # Basic idea here: if we haven't altered setup.py (which is also where
    # deps are kept), then we don't need a new virtualenv. I used to be
    # more clever here, but this ended up being better.
    with open(path.join(root, 'setup.py'), 'r') as f:
        setuppy = f.read()
    assert setuppy
    sha = hashlib.sha1(setuppy).hexdigest()
    venv_name = '{}-{}'.format(prefix, sha)
    venv_path = posixpath.join(FLASK_ROOT, 'venvs', venv_name)
    if not file_exists(venv_path):
        sudo('virtualenv {}'.format(venv_path))
    return venv_name, venv_path

Comments (0)