Fabric deploy script with : south migrations, rollback and maintenance page.
from fabric.api import env, run, sudo, local, put | |
def production(): | |
"""Defines production environment""" | |
env.user = "deploy" | |
env.hosts = ['example.com',] | |
env.base_dir = "/var/www" | |
env.app_name = "app" | |
env.domain_name = "app.example.com" | |
env.domain_path = "%(base_dir)s/%(domain_name)s" % { 'base_dir':env.base_dir, 'domain_name':env.domain_name } | |
env.current_path = "%(domain_path)s/current" % { 'domain_path':env.domain_path } | |
env.releases_path = "%(domain_path)s/releases" % { 'domain_path':env.domain_path } | |
env.shared_path = "%(domain_path)s/shared" % { 'domain_path':env.domain_path } | |
env.git_clone = "git@github.com:example/app.git" | |
env.env_file = "deploy/production.txt" | |
def releases(): | |
"""List a releases made""" | |
env.releases = sorted(run('ls -x %(releases_path)s' % { 'releases_path':env.releases_path }).split()) | |
if len(env.releases) >= 1: | |
env.current_revision = env.releases[-1] | |
env.current_release = "%(releases_path)s/%(current_revision)s" % { 'releases_path':env.releases_path, 'current_revision':env.current_revision } | |
if len(env.releases) > 1: | |
env.previous_revision = env.releases[-2] | |
env.previous_release = "%(releases_path)s/%(previous_revision)s" % { 'releases_path':env.releases_path, 'previous_revision':env.previous_revision } | |
def start(): | |
"""Start the application servers""" | |
sudo("/etc/init.d/apache2 start") | |
def restart(): | |
"""Restarts your application""" | |
sudo("/etc/init.d/apache2 force-reload") | |
def stop(): | |
"""Stop the application servers""" | |
sudo("/etc/init.d/apache2 stop") | |
def permissions(): | |
"""Make the release group-writable""" | |
sudo("chmod -R g+w %(domain_path)s" % { 'domain_path':env.domain_path }) | |
sudo("chown -R www-data:www-data %(domain_path)s" % { 'domain_path':env.domain_path }) | |
def setup(): | |
"""Prepares one or more servers for deployment""" | |
run("mkdir -p %(domain_path)s/{releases,shared}" % { 'domain_path':env.domain_path }) | |
run("mkdir -p %(shared_path)s/{system,log,index}" % { 'shared_path':env.shared_path }) | |
permissions() | |
def checkout(): | |
"""Checkout code to the remote servers""" | |
from time import time | |
env.current_release = "%(releases_path)s/%(time).0f" % { 'releases_path':env.releases_path, 'time':time() } | |
run("cd %(releases_path)s; git clone -q -o deploy --depth 1 %(git_clone)s %(current_release)s" % { 'releases_path':env.releases_path, 'git_clone':env.git_clone, 'current_release':env.current_release }) | |
def update(): | |
"""Copies your project and updates environment and symlink""" | |
update_code() | |
update_env() | |
symlink() | |
permissions() | |
def update_code(): | |
"""Copies your project to the remote servers""" | |
checkout() | |
permissions() | |
def symlink(): | |
"""Updates the symlink to the most recently deployed version""" | |
if not env.has_key('current_release'): | |
releases() | |
run("ln -nfs %(current_release)s %(current_path)s" % { 'current_release':env.current_release, 'current_path':env.current_path }) | |
run("ln -nfs %(shared_path)s/log %(current_release)s/log" % { 'shared_path':env.shared_path, 'current_release':env.current_release }) | |
run("ln -nfs %(shared_path)s/index %(current_release)s/index" % { 'shared_path':env.shared_path, 'current_release':env.current_release }) | |
run("ln -nfs %(shared_path)s/cdlm.db %(current_release)s/cdlm.db" % { 'shared_path':env.shared_path, 'current_release':env.current_release }) | |
run("ln -nfs %(shared_path)s/system/local.py %(current_release)s/%(app_name)s/local.py" % { 'shared_path':env.shared_path, 'current_release':env.current_release, 'app_name':env.app_name }) | |
run("ln -nfs %(current_release)s/env/src/django/django/contrib/admin/media %(current_release)s/%(app_name)s/media/admin" % { 'current_release':env.current_release, 'app_name':env.app_name }) | |
def update_env(): | |
"""Update servers environment on the remote servers""" | |
if not env.has_key('current_release'): | |
releases() | |
run("cd %(current_release)s; virtualenv --no-site-packages --unzip-setuptools env" % { 'current_release':env.current_release }) | |
run("pip -q install -E %(current_release)s/env -r %(current_release)s/%(env_file)s" % { 'current_release':env.current_release, 'env_file':env.env_file }) | |
permissions() | |
def migrate(): | |
"""Run the migrate task""" | |
if not env.has_key('current_release'): | |
releases() | |
run("source %(current_release)s/env/bin/activate; cd %(current_release)s; python %(app_name)s/manage.py migrate" % { 'current_release':env.current_release, 'app_name':env.app_name }) | |
def migrations(): | |
"""Deploy and run pending migrations""" | |
update_code() | |
update_env() | |
migrate() | |
symlink() | |
restart() | |
def cleanup(): | |
"""Clean up old releases""" | |
if not env.has_key('releases'): | |
releases() | |
if len(env.releases) > 3: | |
directories = env.releases | |
directories.reverse() | |
del directories[:3] | |
env.directories = ' '.join([ "%(releases_path)s/%(release)s" % { 'releases_path':env.releases_path, 'release':release } for release in directories ]) | |
run("rm -rf %(directories)s" % { 'directories':env.directories }) | |
def enable(): | |
"""Makes the application web-accessible again""" | |
run("rm %(shared_path)s/system/maintenance.html" % { 'shared_path':env.shared_path }) | |
def disable(**kwargs): | |
"""Present a maintenance page to visitors""" | |
import os, datetime | |
from django.conf import settings | |
try: | |
settings.configure( | |
DEBUG=False, TEMPLATE_DEBUG=False, | |
TEMPLATE_DIRS=(os.path.join(os.getcwd(), 'templates/'),) | |
) | |
except EnvironmentError: | |
pass | |
from django.template.loader import render_to_string | |
env.deadline = kwargs.get('deadline', None) | |
env.reason = kwargs.get('reason', None) | |
open("maintenance.html", "w").write( | |
render_to_string("maintenance.html", { 'now':datetime.datetime.now(), 'deadline':env.deadline, 'reason':env.reason }).encode('utf-8') | |
) | |
put('maintenance.html', '%(shared_path)s/system/maintenance.html' % { 'shared_path':env.shared_path }) | |
local("rm maintenance.html") | |
def rollback_code(): | |
"""Rolls back to the previously deployed version""" | |
if not env.has_key('releases'): | |
releases() | |
if len(env.releases) >= 2: | |
env.current_release = env.releases[-1] | |
env.previous_revision = env.releases[-2] | |
env.current_release = "%(releases_path)s/%(current_revision)s" % { 'releases_path':env.releases_path, 'current_revision':env.current_revision } | |
env.previous_release = "%(releases_path)s/%(previous_revision)s" % { 'releases_path':env.releases_path, 'previous_revision':env.previous_revision } | |
run("rm %(current_path)s; ln -s %(previous_release)s %(current_path)s && rm -rf %(current_release)s" % { 'current_release':env.current_release, 'previous_release':env.previous_release, 'current_path':env.current_path }) | |
def rollback(): | |
"""Rolls back to a previous version and restarts""" | |
rollback_code() | |
restart() | |
def cold(): | |
"""Deploys and starts a `cold' application""" | |
update() | |
migrate() | |
start() | |
def deploy(): | |
"""Deploys your project. This calls both `update' and `restart'""" | |
update() | |
restart() |
This comment has been minimized.
This comment has been minimized.
There is migrate() to be used directly for rollback. |
This comment has been minimized.
This comment has been minimized.
Should add migrate() to the rollback() function in order to rollback any possible migrations. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
rollback()
doesn't roll back South migrationsRunning
python %(app_name)s/manage.py migrate
after symlinking current_path back to previous_release should do the trick