Skip to content

Instantly share code, notes, and snippets.

@bradmontgomery
Created October 31, 2012 17:25
Show Gist options
  • Save bradmontgomery/3988476 to your computer and use it in GitHub Desktop.
Save bradmontgomery/3988476 to your computer and use it in GitHub Desktop.
Sample fabfile used in a django project. Deployment happens via scp from a local directory (long story but I've got apps bundled in a project repo that I want to deploy individually)
from datetime import date
from fabric import colors
from fabric.api import env, local, settings, run, cd
from fabric.context_managers import prefix
from fabric.contrib.files import exists
# Global Variables!
# -----------------
DEFAULT_VENV = 'my_venv'
DEFAULT_VENV_ACTIVATE = "source /path/to/your/.virtualenvs/{0}/bin/activate"
ACTIVATE_VIRTUALENV = DEFAULT_VENV_ACTIVATE.format(DEFAULT_VENV)
REMOTE_PROJECT_DIR = "/path/to/your/project/"
REMOTE_APP_DIR = '{0}myproject/'.format(REMOTE_PROJECT_DIR)
PROD_SETTINGS = 'myproject.settings'
DEV_SETTINGS = 'myproject.local_settings'
env.hosts = ['user@domain']
def manage(management_command, args=None):
""" Run a remote management command.
Note: you can provide more complext arguments by
quoting them, for e.g.:
fab manage:command,"arg1 arg2 --other=settings"
"""
with cd(REMOTE_PROJECT_DIR):
with prefix(ACTIVATE_VIRTUALENV):
run("python manage.py {0} {1} --settings={2}".format(
management_command,
args or '',
PROD_SETTINGS
))
def scp(path):
""" WARNING: uploads files at the given path overwriting remote files
without warning! This can be used to push settings files or anything else.
Examples:
* fab scp:settings.py
* fab scp:"nginx/down.html"
* fab scp:"path/to/whatever/*"
This is also used by the ``upload_app`` task.
"""
recursive = path.endswith("/") or path.endswith("*")
remote_path = path
if remote_path.endswith("*"):
remote_path = remote_path[:-1]
with cd(REMOTE_PROJECT_DIR):
for h in env.hosts:
cmd = 'scp {options} {local_path} {host}:{remote_dir}{remote_path}'
cmd = cmd.format(
options="-r" if recursive else "", # needed for dirs
local_path=path,
host=h,
remote_dir=REMOTE_PROJECT_DIR,
remote_path=remote_path
)
rmpyc("local") # NEVER upload .pyc files
local(cmd)
def down():
""" put the site into *maintenance mode* """
with cd("/etc/nginx/sites-enabled"):
if exists("production.conf"):
run("rm production.conf")
run("ln -s /etc/nginx/sites-available/down.conf down.conf")
run("/etc/init.d/nginx restart")
def up():
""" pull the site out of *maintenance mode* """
with cd("/etc/nginx/sites-enabled"):
if exists("production.conf"):
run("rm production.conf")
if exists("down.conf"):
run("rm down.conf")
run("ln -s /etc/nginx/sites-available/production.conf production.conf")
run("/etc/init.d/nginx restart")
def migrate(app, list=False, settings=PROD_SETTINGS):
""" List or run (default) south migrations for the given app.
You can also specify settings:
fab migrate:appname - runs the migration for the given app
fab migrate:appname,list - will list migrations for the given app
fab migrate:appname,list,settings_foo - list migrations for the given app,
using "--settings=settings_foo"
"""
with prefix(ACTIVATE_VIRTUALENV):
with cd(REMOTE_PROJECT_DIR):
if app == "list":
app = ''
list = True
cmd = "python manage.py migrate {0}".format(app)
if list:
cmd += " --list"
cmd += " --settings={0}".format(settings)
run(cmd)
def backup_remote_app(app):
""" bundle the given app in a timestamped .tgz """
app_dir = "{0}{1}".format(REMOTE_APP_DIR, app)
backup_file = "/backups/backup-{0}-{1}.tgz".format(app, date.today())
if exists(backup_file):
run("rm {0}".format(backup_file))
msg = "Removing previous backup: {0}".format(backup_file)
print(colors.yellow(msg))
if exists(app_dir):
with cd(app_dir):
cmd = "tar -czf {0} *".format(backup_file)
run(cmd)
msg = "Backed up existing app: {0}".format(app_dir)
print(colors.yellow(msg))
else:
msg = "Dir does not exist, skipping backup: {0}".format(app_dir)
print(colors.yellow(msg))
def rollback_app(app):
""" extract a backed up app (see backup_remote_app) NOTE: this only
works on the same day an app was uploaded """
rem = "{0}{1}".format(REMOTE_APP_DIR, app)
backup_file = "/backups/backup-{0}-{1}.tgz".format(app, date.today())
if exists(backup_file):
with cd(rem):
run("tar -xzf {0}".format(backup_file))
print(colors.green("Backup restored! Restarting service..."))
reload_gunicorn()
else:
msg = "Backup file does not exist: {0}".format(backup_file)
print(colors.red(msg))
def upload_app(app):
"""Upload content in the given app directory """
app_dir = "{0}{1}".format(REMOTE_APP_DIR, app)
if not exists(app_dir):
run("mkdir -p {0}".format(app_dir))
msg = "Created remote app directory: {0}".format(app_dir)
print(colors.yellow(msg))
loc = "myproject/{0}/*".format(app)
scp(loc)
def reload_gunicorn():
"""Restart Gunicorn."""
run("killall gunicorn")
def rmpyc(where="remote"):
""" Remove all .pyc files. Takes a ``where`` param [remote|local] """
if where == "remote":
with cd(REMOTE_PROJECT_DIR):
run('find ./ -type f -name "*.pyc" -exec rm {} \;')
print(colors.yellow("Removed remote .pyc files"))
else:
local('find ./ -type f -name "*.pyc" -exec rm {} \;')
print(colors.yellow("Removed local .pyc files"))
def deploy(app):
"""Deploy a single app."""
# Test first. If this fails, fabric will abort.
test(app)
# Backup the existing app. This fails gracefully
# if it doesn't exist.
backup_remote_app(app)
upload_app(app)
rmpyc(where='remote')
reload_gunicorn()
def test(apps, django_settings=None):
""" Just a wrapper to run tests locally and print green/red output
based on success/failure
Inspired by: http://goo.gl/3xdJD
"""
with settings(warn_only=True):
data = {
'settings': django_settings if django_settings else DEV_SETTINGS,
'apps': apps
}
c = './manage.py test %(apps)s --settings=%(settings)s -v 2 --failfast'
result = local(c % data, capture=False)
assert not result.failed, colors.red("Tests Failed! :(")
print(colors.green("Tests Passed :)"))
def install_requirements(venv=None, requirements="requirements.txt"):
"""pip-install from a requirements file"""
with(cd(REMOTE_PROJECT_DIR)):
activenv = DEFAULT_VENV_ACTIVATE.format(venv) if venv else DEFAULT_VENV
with prefix(activenv):
cmd = "pip install -r {req}".format(req=requirements)
run(cmd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment