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 all needed directories.
30 require('hosts', 'app_path')
32 if not files.exists(env.app_path):
33 run('mkdir -p %(app_path)s' % env, pty=True)
34 with cd(env.app_path):
35 for subdir in 'releases', 'packages', 'log':
36 if not files.exists(subdir):
37 run('mkdir -p %s' % subdir, pty=True)
38 with cd('%(app_path)s/releases' % env):
39 if not files.exists('current'):
40 run('ln -sfT . current', pty=True)
41 if not files.exists('previous'):
42 run('ln -sfT . previous', pty=True)
47 def check_localsettings():
48 if not files.exists('%(app_path)s/localsettings.py' % env):
49 abort('localsettings.py file missing.')
55 Deploy the latest version of the site to the servers,
56 install any required third party modules,
57 install the virtual host and then restart the webserver
59 require('hosts', 'app_path')
62 env.release = time.strftime('%Y-%m-%dT%H%M')
68 install_requirements()
69 symlink_current_release()
77 Limited rollback capability. Simple loads the previously current
78 version of the code. Rolling back again will swap between the two.
79 Warning: this will almost certainly go wrong, it there were any migrations
82 require('hosts', 'app_path')
84 run('mv releases/current releases/_previous;', pty=True)
85 run('mv releases/previous releases/current;', pty=True)
86 run('mv releases/_previous releases/previous;', pty=True)
91 def deploy_version(version):
93 Loads the specified version.
94 Warning: this will almost certainly go wrong, it there were any migrations
97 "Specify a specific version to be made live"
98 require('hosts', 'app_path')
101 run('rm releases/previous; mv releases/current releases/previous;', pty=True)
102 run('ln -s %(version)s releases/current' % env, pty=True)
109 for service in env.services:
113 # =====================================================================
114 # = Helpers. These are called by other functions rather than directly =
115 # =====================================================================
118 def upload_sample(self):
121 class DebianGunicorn(Service):
122 def __init__(self, name):
123 super(Task, self).__init__()
127 print '>>> restart webserver using gunicorn-debian'
128 sudo('gunicorn-debian restart %s' % self.name, shell=False)
130 def upload_sample(self):
131 upload_sample('gunicorn')
133 class Apache(Service):
135 print '>>> restart webserver by touching WSGI'
137 run('touch %(app_path)s/%(project_name)s/wsgi.py' % env)
139 class Supervisord(Service):
140 def __init__(self, name):
141 super(Task, self).__init__()
145 print '>>> supervisord: restart %s' % self.name
146 sudo('supervisorctl restart %s' % self.name, shell=False)
148 def upload_samples():
149 upload_localsettings_sample()
150 upload_nginx_sample()
151 for service in env.services:
152 service.upload_sample()
154 def upload_sample(name):
155 require('app_path', 'project_name')
156 upload_path = '%(app_path)s/' % env + name + '.sample'
157 if files.exists(upload_path):
159 print '>>> upload %s template' % name
160 template = '%(project_name)s/' % env + name + '.template'
161 if not exists(template):
162 template = join(dirname(abspath(__file__)), 'templates/' + name + '.template')
163 files.upload_template(template, upload_path, env)
165 def upload_localsettings_sample():
166 "Fill out localsettings template and upload as a sample."
167 env.secret_key = get_random_string(50)
168 upload_sample('localsettings.py')
170 upload_nginx_sample = lambda: upload_sample('nginx')
172 def upload_tar_from_git():
173 "Create an archive from the current Git branch and upload it"
174 print '>>> upload tar from git'
175 require('release', provided_by=[deploy])
177 local('git-archive-all.sh --format tar %(release)s.tar' % env)
178 local('gzip %(release)s.tar' % env)
179 run('mkdir -p %(app_path)s/releases/%(release)s' % env, pty=True)
180 run('mkdir -p %(app_path)s/packages' % env, pty=True)
181 put('%(release)s.tar.gz' % env, '%(app_path)s/packages/' % env)
182 run('cd %(app_path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
183 local('rm %(release)s.tar.gz' % env)
185 def install_requirements():
186 "Install the required packages from the requirements file using pip"
187 print '>>> install requirements'
188 require('release', provided_by=[deploy])
190 if not files.exists('%(app_path)s/ve' % env):
191 require('virtualenv')
192 run('%(virtualenv)s %(app_path)s/ve' % env, pty=True)
193 with cd('%(app_path)s/releases/%(release)s' % env):
194 run('%(app_path)s/ve/bin/pip install -r requirements.txt' % env, pty=True)
195 # Install DB requirement
197 'django.db.backends.postgresql_psycopg2': 'psycopg2',
198 'django.db.backends.mysql': 'MySQL-python',
200 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)
201 for database in databases.split():
202 if database in database_reqs:
203 # TODO: set pip default pypi
204 run('%(app_path)s/ve/bin/pip install ' % env + database_reqs[database])
207 def copy_localsettings():
208 "Copy localsettings.py from root directory to release directory (if this file exists)"
209 print ">>> copy localsettings"
210 require('release', provided_by=[deploy])
211 require('app_path', 'project_name')
213 with settings(warn_only=True):
214 run('cp %(app_path)s/localsettings.py %(app_path)s/releases/%(release)s/%(project_name)s' % env)
216 def symlink_current_release():
217 "Symlink our current release"
218 print '>>> symlink current release'
219 require('release', provided_by=[deploy])
221 with cd(env.app_path):
222 run('rm releases/previous; mv releases/current releases/previous')
223 run('ln -s %(release)s releases/current' % env)
226 "Update the database"
228 require('app_path', 'project_name')
229 with cd('%(app_path)s/releases/current' % env):
230 run('%(app_path)s/ve/bin/python manage.py syncdb --noinput' % env, pty=True)
231 run('%(app_path)s/ve/bin/python manage.py migrate' % env, pty=True)
234 """Collect static files"""
235 print '>>> collectstatic'
236 require('app_path', 'project_name')
237 with cd('%(app_path)s/releases/current' % env):
238 run('%(app_path)s/ve/bin/python manage.py collectstatic --noinput' % env, pty=True)