2 Generic fabric deployment script.
3 Create a fabfile.py in the project and start it with:
5 from fnpdjango.deploy import *
7 Then set up some env properties:
8 project_name: slug-like project name
9 hosts: list of target host names
10 user: remote user name
11 app_path: where does the app go
12 services: list of tasks to run after deployment
15 from os.path import abspath, dirname, exists, join
16 from django.utils.crypto import get_random_string
17 from fabric.api import *
18 from fabric.contrib import files
19 from fabric.tasks import Task, execute
21 env.virtualenv = '/usr/bin/virtualenv'
28 Setup a fresh virtualenv as well as a few useful directories.
29 virtualenv should be already installed.
31 require('hosts', 'app_path', 'virtualenv')
33 run('mkdir -p %(app_path)s' % env, pty=True)
34 run('%(virtualenv)s %(app_path)s/ve' % env, pty=True)
35 run('mkdir -p %(app_path)s/releases %(app_path)s/packages %(app_path)s/log' % env, pty=True)
36 run('cd %(app_path)s/releases; ln -sfT . current; ln -sfT . previous' % env, pty=True)
38 print "Fill out db details in localsettings.py and run deploy."
44 Deploy the latest version of the site to the servers,
45 install any required third party modules,
46 install the virtual host and then restart the webserver
48 require('hosts', 'app_path')
51 env.release = time.strftime('%Y-%m-%dT%H%M')
55 install_requirements()
57 symlink_current_release()
65 Limited rollback capability. Simple loads the previously current
66 version of the code. Rolling back again will swap between the two.
67 Warning: this will almost certainly go wrong, it there were any migrations
70 require('hosts', 'app_path')
72 run('mv releases/current releases/_previous;', pty=True)
73 run('mv releases/previous releases/current;', pty=True)
74 run('mv releases/_previous releases/previous;', pty=True)
79 def deploy_version(version):
81 Loads the specified version.
82 Warning: this will almost certainly go wrong, it there were any migrations
85 "Specify a specific version to be made live"
86 require('hosts', 'app_path')
89 run('rm releases/previous; mv releases/current releases/previous;', pty=True)
90 run('ln -s %(version)s releases/current' % env, pty=True)
97 for service in env.services:
101 # =====================================================================
102 # = Helpers. These are called by other functions rather than directly =
103 # =====================================================================
104 class DebianGunicorn(Task):
105 def __init__(self, name):
106 super(Task, self).__init__()
110 print '>>> restart webserver using gunicorn-debian'
111 sudo('gunicorn-debian restart %s' % self.name, shell=False)
115 print '>>> restart webserver by touching WSGI'
117 run('touch %(app_path)s/%(project_name)s/wsgi.py' % env)
119 class Supervisord(Task):
120 def __init__(self, name):
121 super(Task, self).__init__()
125 print '>>> supervisord: restart %s' % self.name
126 sudo('supervisorctl restart %s' % self.name, shell=False)
131 run('[ -e %(app_path)s/ve ]' % env)
133 print "Environment isn't ready. Run `fab setup` first."
136 def upload_samples():
137 upload_localsettings_sample()
138 upload_nginx_sample()
139 upload_gunicorn_sample()
141 def upload_localsettings_sample():
142 "Fill out localsettings template and upload as a sample."
143 print '>>> upload localsettings template'
144 require('app_path', 'project_name')
145 template = '%(project_name)s/localsettings.py.template'
146 if not exists(template):
147 template = join(dirname(abspath(__file__)), 'templates/localsettings.py.template')
148 env.secret_key = get_random_string(50)
149 files.upload_template(template, '%(app_path)s/localsettings.py.sample' % env, env)
151 def upload_nginx_sample():
152 "Fill out nginx conf template and upload as a sample."
153 print '>>> upload nginx template'
154 require('app_path', 'project_name')
155 template = '%(project_name)s/nginx.template'
156 if not exists(template):
157 template = join(dirname(abspath(__file__)), 'templates/nginx.template')
158 files.upload_template(template, '%(app_path)s/nginx.sample' % env, env)
160 def upload_gunicorn_sample():
161 "Fill out gunicorn conf template and upload as a sample."
162 print '>>> upload gunicorn template'
163 require('app_path', 'project_name')
164 template = '%(project_name)s/gunicorn.template'
165 if not exists(template):
166 template = join(dirname(abspath(__file__)), 'templates/gunicorn.template')
167 files.upload_template(template, '%(app_path)s/gunicorn.sample' % env, env)
169 def upload_tar_from_git():
170 "Create an archive from the current Git branch and upload it"
171 print '>>> upload tar from git'
172 require('release', provided_by=[deploy])
174 local('git-archive-all.sh --format tar %(release)s.tar' % env)
175 local('gzip %(release)s.tar' % env)
176 run('mkdir -p %(app_path)s/releases/%(release)s' % env, pty=True)
177 run('mkdir -p %(app_path)s/packages' % env, pty=True)
178 put('%(release)s.tar.gz' % env, '%(app_path)s/packages/' % env)
179 run('cd %(app_path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
180 local('rm %(release)s.tar.gz' % env)
182 def install_requirements():
183 "Install the required packages from the requirements file using pip"
184 print '>>> install requirements'
185 require('release', provided_by=[deploy])
187 run('cd %(app_path)s; ve/bin/pip install -r %(app_path)s/releases/%(release)s/requirements.txt' % env, pty=True)
189 def copy_localsettings():
190 "Copy localsettings.py from root directory to release directory (if this file exists)"
191 print ">>> copy localsettings"
192 require('release', provided_by=[deploy])
193 require('app_path', 'project_name')
195 with settings(warn_only=True):
196 run('cp %(app_path)s/localsettings.py %(app_path)s/releases/%(release)s/%(project_name)s' % env)
198 def symlink_current_release():
199 "Symlink our current release"
200 print '>>> symlink current release'
201 require('release', provided_by=[deploy])
203 with cd(env.app_path):
204 run('rm releases/previous; mv releases/current releases/previous')
205 run('ln -s %(release)s releases/current' % env)
208 "Update the database"
210 require('app_path', 'project_name')
211 with cd('%(app_path)s/releases/current' % env):
212 run('%(app_path)s/ve/bin/python manage.py syncdb --noinput' % env, pty=True)
213 run('%(app_path)s/ve/bin/python manage.py migrate' % env, pty=True)
216 """Collect static files"""
217 print '>>> collectstatic'
218 require('app_path', 'project_name')
219 with cd('%(app_path)s/releases/current' % env):
220 run('%(app_path)s/ve/bin/python manage.py collectstatic --noinput' % env, pty=True)