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