Created
October 31, 2012 17:25
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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