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 '.')
16 localsettings_dst_path (optional): path indicating
17 where to copy the localsettings file, relative
18 to django_root_path (defaults to project_name/localsettings.py)
20 from os.path import abspath, dirname, exists, join
21 from django.utils.crypto import get_random_string
22 from fabric.api import *
23 from fabric.contrib import files
24 from fabric.tasks import Task, execute
26 env.virtualenv = '/usr/bin/virtualenv'
33 Setup all needed directories.
35 require('hosts', 'app_path')
37 if not files.exists(env.app_path):
38 run('mkdir -p %(app_path)s' % env, pty=True)
39 with cd(env.app_path):
40 for subdir in 'releases', 'packages', 'log':
41 if not files.exists(subdir):
42 run('mkdir -p %s' % subdir, pty=True)
43 with cd('%(app_path)s/releases' % env):
44 if not files.exists('current'):
45 run('ln -sfT . current', pty=True)
46 if not files.exists('previous'):
47 run('ln -sfT . previous', pty=True)
52 def check_localsettings():
53 if not files.exists('%(app_path)s/localsettings.py' % env):
54 abort('localsettings.py file missing.')
60 Deploy the latest version of the site to the servers,
61 install any required third party modules,
62 install the virtual host and then restart the webserver
64 require('hosts', 'app_path')
67 env.release = time.strftime('%Y-%m-%dT%H%M')
73 install_requirements()
74 symlink_current_release()
82 Limited rollback capability. Simple loads the previously current
83 version of the code. Rolling back again will swap between the two.
84 Warning: this will almost certainly go wrong, it there were any migrations
87 require('hosts', 'app_path')
89 run('mv releases/current releases/_previous;', pty=True)
90 run('mv releases/previous releases/current;', pty=True)
91 run('mv releases/_previous releases/previous;', pty=True)
96 def deploy_version(version):
98 Loads the specified version.
99 Warning: this will almost certainly go wrong, it there were any migrations
102 "Specify a specific version to be made live"
103 require('hosts', 'app_path')
104 env.version = version
106 run('rm releases/previous; mv releases/current releases/previous;', pty=True)
107 run('ln -s %(version)s releases/current' % env, pty=True)
114 for service in env.services:
118 # =====================================================================
119 # = Helpers. These are called by other functions rather than directly =
120 # =====================================================================
123 def upload_sample(self):
126 class DebianGunicorn(Service):
127 def __init__(self, name):
128 super(Task, self).__init__()
132 print '>>> restart webserver using gunicorn-debian'
133 sudo('gunicorn-debian restart %s' % self.name, shell=False)
135 def upload_sample(self):
136 upload_sample('gunicorn')
138 class Apache(Service):
140 print '>>> restart webserver by touching WSGI'
142 run('touch %(app_path)s/%(project_name)s/wsgi.py' % env)
144 class Supervisord(Service):
145 def __init__(self, name):
146 super(Task, self).__init__()
150 print '>>> supervisord: restart %s' % self.name
151 sudo('supervisorctl restart %s' % self.name, shell=False)
153 def upload_samples():
154 upload_localsettings_sample()
155 upload_nginx_sample()
156 for service in env.services:
157 service.upload_sample()
159 def upload_sample(name):
160 require('app_path', 'project_name')
161 upload_path = '%(app_path)s/' % env + name + '.sample'
162 if files.exists(upload_path):
164 print '>>> upload %s template' % name
165 template = '%(project_name)s/' % env + name + '.template'
166 if not exists(template):
167 template = join(dirname(abspath(__file__)), 'templates/' + name + '.template')
168 files.upload_template(template, upload_path, env)
170 def upload_localsettings_sample():
171 "Fill out localsettings template and upload as a sample."
172 env.secret_key = get_random_string(50)
173 upload_sample('localsettings.py')
175 upload_nginx_sample = lambda: upload_sample('nginx')
177 def upload_tar_from_git():
178 "Create an archive from the current Git branch and upload it"
179 print '>>> upload tar from git'
180 require('release', provided_by=[deploy])
182 local('git-archive-all.sh --format tar %(release)s.tar' % env)
183 local('gzip %(release)s.tar' % env)
184 run('mkdir -p %(app_path)s/releases/%(release)s' % env, pty=True)
185 run('mkdir -p %(app_path)s/packages' % env, pty=True)
186 put('%(release)s.tar.gz' % env, '%(app_path)s/packages/' % env)
187 run('cd %(app_path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
188 local('rm %(release)s.tar.gz' % env)
190 def install_requirements():
191 "Install the required packages from the requirements file using pip"
192 print '>>> install requirements'
193 require('release', provided_by=[deploy])
195 if not files.exists('%(app_path)s/ve' % env):
196 require('virtualenv')
197 run('%(virtualenv)s %(app_path)s/ve' % env, pty=True)
198 with cd('%(app_path)s/releases/%(release)s' % env):
199 run('%(app_path)s/ve/bin/pip install -r requirements.txt' % env, pty=True)
200 with cd(get_django_root_path(env['release'])):
201 # Install DB requirement
203 'django.db.backends.postgresql_psycopg2': 'psycopg2',
204 'django.db.backends.mysql': 'MySQL-python',
206 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)
207 for database in databases.split():
208 if database in database_reqs:
209 # TODO: set pip default pypi
210 run('%(app_path)s/ve/bin/pip install ' % env + database_reqs[database])
213 def copy_localsettings():
214 "Copy localsettings.py from root directory to release directory (if this file exists)"
215 print ">>> copy localsettings"
216 require('release', provided_by=[deploy])
217 require('app_path', 'project_name')
219 with settings(warn_only=True):
220 copy_to = join(get_django_root_path(env['release']), env.get('localsettings_dst_path', env['project_name']))
221 run('cp %(app_path)s/localsettings.py ' % env + copy_to)
223 def symlink_current_release():
224 "Symlink our current release"
225 print '>>> symlink current release'
226 require('release', provided_by=[deploy])
228 with cd(env.app_path):
229 run('rm releases/previous; mv releases/current releases/previous')
230 run('ln -s %(release)s releases/current' % env)
233 "Update the database"
235 require('app_path', 'project_name')
236 with cd(get_django_root_path('current')):
237 run('%(app_path)s/ve/bin/python manage.py syncdb --noinput' % env, pty=True)
238 run('%(app_path)s/ve/bin/python manage.py migrate' % env, pty=True)
241 """Collect static files"""
242 print '>>> collectstatic'
243 require('app_path', 'project_name')
244 with cd(get_django_root_path('current')):
245 run('%(app_path)s/ve/bin/python manage.py collectstatic --noinput' % env, pty=True)
248 def get_django_root_path(release):
250 path = '%(app_path)s/releases/%(release)s' % dict(app_path = env['app_path'], release = release)
251 if 'django_root_path' in env:
252 path = join(path, env['django_root_path'])