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
13 django_root_path (optional): path to the directory
14 containing django project, relative to the
15 root of the repository (defaults to '.')
17 from os.path import abspath, dirname, exists, join
18 from django.utils.crypto import get_random_string
19 from fabric.api import *
20 from fabric.contrib import files
21 from fabric.tasks import Task, execute
23 env.virtualenv = '/usr/bin/virtualenv'
30 Setup all needed directories.
32 require('hosts', 'app_path')
34 if not files.exists(env.app_path):
35 run('mkdir -p %(app_path)s' % env, pty=True)
36 with cd(env.app_path):
37 for subdir in 'releases', 'packages', 'log':
38 if not files.exists(subdir):
39 run('mkdir -p %s' % subdir, pty=True)
40 with cd('%(app_path)s/releases' % env):
41 if not files.exists('current'):
42 run('ln -sfT . current', pty=True)
43 if not files.exists('previous'):
44 run('ln -sfT . previous', pty=True)
49 def check_localsettings():
50 if not files.exists('%(app_path)s/localsettings.py' % env):
51 abort('localsettings.py file missing.')
57 Deploy the latest version of the site to the servers,
58 install any required third party modules,
59 install the virtual host and then restart the webserver
61 require('hosts', 'app_path')
64 env.release = time.strftime('%Y-%m-%dT%H%M')
70 install_requirements()
71 symlink_current_release()
79 Limited rollback capability. Simple loads the previously current
80 version of the code. Rolling back again will swap between the two.
81 Warning: this will almost certainly go wrong, it there were any migrations
84 require('hosts', 'app_path')
86 run('mv releases/current releases/_previous;', pty=True)
87 run('mv releases/previous releases/current;', pty=True)
88 run('mv releases/_previous releases/previous;', pty=True)
93 def deploy_version(version):
95 Loads the specified version.
96 Warning: this will almost certainly go wrong, it there were any migrations
99 "Specify a specific version to be made live"
100 require('hosts', 'app_path')
101 env.version = version
103 run('rm releases/previous; mv releases/current releases/previous;', pty=True)
104 run('ln -s %(version)s releases/current' % env, pty=True)
111 for service in env.services:
115 # =====================================================================
116 # = Helpers. These are called by other functions rather than directly =
117 # =====================================================================
120 def upload_sample(self):
123 class DebianGunicorn(Service):
124 def __init__(self, name):
125 super(Task, self).__init__()
129 print '>>> restart webserver using gunicorn-debian'
130 sudo('gunicorn-debian restart %s' % self.name, shell=False)
132 def upload_sample(self):
133 upload_sample('gunicorn')
135 class Apache(Service):
137 print '>>> restart webserver by touching WSGI'
139 run('touch %(app_path)s/%(project_name)s/wsgi.py' % env)
141 class Supervisord(Service):
142 def __init__(self, name):
143 super(Task, self).__init__()
147 print '>>> supervisord: restart %s' % self.name
148 sudo('supervisorctl restart %s' % self.name, shell=False)
150 def upload_samples():
151 upload_localsettings_sample()
152 upload_nginx_sample()
153 for service in env.services:
154 service.upload_sample()
156 def upload_sample(name):
157 require('app_path', 'project_name')
158 upload_path = '%(app_path)s/' % env + name + '.sample'
159 if files.exists(upload_path):
161 print '>>> upload %s template' % name
162 template = '%(project_name)s/' % env + name + '.template'
163 if not exists(template):
164 template = join(dirname(abspath(__file__)), 'templates/' + name + '.template')
165 files.upload_template(template, upload_path, env)
167 def upload_localsettings_sample():
168 "Fill out localsettings template and upload as a sample."
169 env.secret_key = get_random_string(50)
170 upload_sample('localsettings.py')
172 upload_nginx_sample = lambda: upload_sample('nginx')
174 def upload_tar_from_git():
175 "Create an archive from the current Git branch and upload it"
176 print '>>> upload tar from git'
177 require('release', provided_by=[deploy])
179 local('git-archive-all.sh --format tar %(release)s.tar' % env)
180 local('gzip %(release)s.tar' % env)
181 run('mkdir -p %(app_path)s/releases/%(release)s' % env, pty=True)
182 run('mkdir -p %(app_path)s/packages' % env, pty=True)
183 put('%(release)s.tar.gz' % env, '%(app_path)s/packages/' % env)
184 run('cd %(app_path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
185 local('rm %(release)s.tar.gz' % env)
187 def install_requirements():
188 "Install the required packages from the requirements file using pip"
189 print '>>> install requirements'
190 require('release', provided_by=[deploy])
192 if not files.exists('%(app_path)s/ve' % env):
193 require('virtualenv')
194 run('%(virtualenv)s %(app_path)s/ve' % env, pty=True)
195 with cd('%(app_path)s/releases/%(release)s' % env):
196 run('%(app_path)s/ve/bin/pip install -r requirements.txt' % env, pty=True)
197 with cd(get_django_root_path(env['release'])):
198 # Install DB requirement
200 'django.db.backends.postgresql_psycopg2': 'psycopg2',
201 'django.db.backends.mysql': 'MySQL-python',
203 databases = run('''DJANGO_SETTINGS_MODULE=%(project_name)s.settings %(app_path)s/ve/bin/python -c 'from django.conf import settings; print " ".join(set([d["ENGINE"] for d in settings.DATABASES.values()]))' ''' % env)
204 for database in databases.split():
205 if database in database_reqs:
206 # TODO: set pip default pypi
207 run('%(app_path)s/ve/bin/pip install ' % env + database_reqs[database])
210 def copy_localsettings():
211 "Copy localsettings.py from root directory to release directory (if this file exists)"
212 print ">>> copy localsettings"
213 require('release', provided_by=[deploy])
214 require('app_path', 'project_name')
216 with settings(warn_only=True):
217 copy_to = join(get_django_root_path(env['release']), env['project_name'])
218 run('cp %(app_path)s/localsettings.py ' % env + copy_to)
220 def symlink_current_release():
221 "Symlink our current release"
222 print '>>> symlink current release'
223 require('release', provided_by=[deploy])
225 with cd(env.app_path):
226 run('rm releases/previous; mv releases/current releases/previous')
227 run('ln -s %(release)s releases/current' % env)
230 "Update the database"
232 require('app_path', 'project_name')
233 with cd(get_django_root_path('current')):
234 run('%(app_path)s/ve/bin/python manage.py syncdb --noinput' % env, pty=True)
235 run('%(app_path)s/ve/bin/python manage.py migrate' % env, pty=True)
238 """Collect static files"""
239 print '>>> collectstatic'
240 require('app_path', 'project_name')
241 with cd(get_django_root_path('current')):
242 run('%(app_path)s/ve/bin/python manage.py collectstatic --noinput' % env, pty=True)
245 def get_django_root_path(release):
247 path = '%(app_path)s/releases/%(release)s' % dict(app_path = env['app_path'], release = release)
248 if 'django_root_path' in env:
249 path = join(path, env['django_root_path'])