forked from pingali/bhagirath
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fabfile.py
352 lines (286 loc) · 12.7 KB
/
fabfile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
fabfile for Django
------------------
see http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/
modified for fabric 0.9/1.0 by Hraban (fiëé visuëlle)
several additions, corrections and customizations, too
!!! OBSOLETE WARNING
this is now superseded by my generic django project:
http://github.com/fiee/generic_django_project
including a heavily enhanced fabfile
!!!
This fabric file makes setting up and deploying a Django application much
easier, but it does make a few assumptions. Namely that you're using Git,
Apache and `mod_wsgi` and you're using Debian or Ubuntu. Also you should have
Django installed on your local machine and SSH installed on both the local
machine and any servers you want to deploy to.
_note that I've used the name project_name throughout this example. Replace
this with whatever your project is called._
First step is to create your project locally:
mkdir project_name
cd project_name
django-admin.py startproject project_name
Now add a requirements file so pip knows to install Django. You'll probably
add other required modules in here later. Create a file called `requirements.txt`
and save it at the top level with the following contents:
Django
(Add other requirements at will, e.g. django-tinymce)
Then save this `fabfile.py` file (this gist!) in the top level directory
which should give you:
project_name/
fabfile.py
requirements.txt
project_name/
__init__.py
manage.py
settings.py
urls.py
You'll need a WSGI file called `django.wsgi`. It will probably look
like the following, depending on your specific paths and the location
of your settings module
import os
import sys
# put the Django project on sys.path
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))))
os.environ["DJANGO_SETTINGS_MODULE"] = "project_name.settings"
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
Last but not least you'll want a virtualhost file for apache which looks
something like the following. Save this as `vhost.conf` in the inner directory.
You'll want to change `/path/to/project_name/` to the location on the remote
server you intent to deploy to.
<VirtualHost *:80>
ServerName www.project_name.com
ServerAlias project_name.example.com # temporary location
# disable listing and "guessing" of static files
<Directory /var/www/>
Options -Indexes FollowSymLinks -MultiViews
AllowOverride None
Order deny,allow
Allow from all
</Directory>
Alias /favicon.ico /var/www/project_name/releases/current/project_name/static/favicon.ico
# project media
Alias /media /var/www/project_name/releases/current/project_name/static
<Location "/media">
SetHandler None
</Location>
# general admin media
Alias /django_admin_media /var/www/project_name/lib/python2.5/site-packages/django/contrib/admin/media
<Location "/django_admin_media">
SetHandler None
</Location>
WSGIDaemonProcess project_name-production user=project_name group=project_name threads=10 python-path=/var/www/project_name/lib/python2.5/site-packages:/var/www/python
WSGIProcessGroup project_name-production
WSGIScriptAlias / /var/www/project_name/releases/current/django.wsgi
ErrorLog /var/www/project_name/logs/error.log
LogLevel warn
CustomLog /var/www/project_name/logs/access.log combined
</VirtualHost>
Now create a file called `.gitignore`, containing the following. This
prevents the compiled python code being included in the repository and
the archive we use for deployment. (Add more that you don't want to
publish, e.g. your local settings and development database.)
*.pyc
*.pyo
.tmp*
E.g. here's my default `.gitignore` file:
*.pyc
*.pyo
.tmp*
.DS_Store
.project
.pydevproject
.settings
.svn
secret.txt
settings_local.py
dev.db
build
*.tar.gz
*.bak
You should now be ready to initialise a git repository in the top
level project_name directory.
git init
git add .gitignore *
git commit -m "Initial commit"
All of that should leave you with
project_name/
.git/
.gitignore
requirements.txt
fabfile.py
project_name/
__init__.py
project_name
django.wsgi
manage.py
settings.py
urls.py
In reality you might prefer to keep your wsgi files and virtual host files
elsewhere. The fabfile has a variable (config.virtualhost_path) for this case.
You'll also want to set the hosts that you intend to deploy to (config.hosts)
as well as the user (config.user).
The first task we're interested in is called setup. It installs all the
required software on the remote machine, then deploys your code and restarts
the webserver.
fab local setup
_note: Since you can't properly setup the required user and database via fab anyway,
it may be better to comment the lines calling aptitude._
Here's what I do to create user and database:
(I re-activated the wheel group for sudo-enabled users. That's a potential security risk!)
adduser --ingroup wheel project_name
mysql -u root -p
create user 'project_name'@'localhost' identified by 'password';
create database proejct_name character set 'utf-8';
use project_name;
grant all privileges on project_name.* to 'project_name'@'localhost';
quit;
After you've made a few changes and commit them to the master Git branch you
can run to deploy the changes.
fab local deploy
If something is wrong then you can rollback to the previous version.
fab local rollback
Note that this only allows you to rollback to the release immediately before
the latest one. If you want to pick a arbitrary release then you can use the
following, where 20090727170527 is a timestamp for an existing release.
fab local deploy_version:20090727170527
If you want to ensure your tests run before you make a deployment then you can
do the following.
fab local test deploy
"""
from __future__ import with_statement # needed for python 2.5
from fabric.api import *
from fabric.contrib.files import exists, append
from fabric.operations import put, sudo
# globals
env.project_name = 'bhagirath'
# environments
def localhost():
"Use the local virtual server"
env.hosts = ['localhost']
env.user = 'ubuntu'
env.path = '/home/%(user)s/workspace/%(project_name)s' % env
env.virtualhost_path = env.path
env.disable_known_hosts = True
env.warn_only = True
env.show = ['running', 'warnings']
env.key_filename = ["/home/ubuntu/.ssh/id_dsa"]
def webserver():
"Use the actual webserver"
env.hosts = ['www.translate4india.com']
env.user = 'ubuntu'
env.path = '/home/%(user)s/workspace/%(project_name)s' % env
env.virtualhost_path = env.path
env.disable_known_hosts = True
env.warn_only = True
env.show = ['running', 'warnings']
env.key_filename = ["/home/ubuntu/.ssh/id_dsa"]
# tasks
def test():
"Run the test suite and bail out if it fails"
result = local("cd %(path)s; python manage.py test" % env) #, fail="abort")
def setup():
"""
Setup a fresh virtualenv as well as a few useful directories, then run
a full deployment
"""
require('hosts', provided_by=[localhost,webserver])
require('path')
sudo('apt-get -y install build-essential libxml2 libxml2-dev libxslt1 libxslt1-dev')
sudo('apt-get -y install build-essential python-dev python-setuptools')
sudo('easy_install pip')
sudo('pip install virtualenv')
sudo('aptitude install -y apache2')
sudo('aptitude install -y libapache2-mod-wsgi') # beware, outdated on hardy!
# we want to get rid of the default apache config
sudo('cd /etc/apache2/sites-available/; a2dissite default;', pty=True)
sudo('a2enmod authz_host alias wsgi dir autoindex ssl rewrite', pty=True)
sudo('mkdir -p %(path)s; chown %(user)s:%(user)s %(path)s;' % env, pty=True)
run('ln -fs %(path)s www;' % env, pty=True) # symlink web dir in home
with cd(env.path):
run('virtualenv --clear --distribute .;' % env, pty=True)
run('mkdir -p logs; mkdir -p releases; mkdir -p shared; mkdir -p packages; ' % env, pty=True)
run('touch logs/error.log; touch logs/django.log; touch logs/access.log ; chmod -R a+w logs; chmod -R a+w shared; ' % env, pty=True)
run('cd releases; rm current; ln -fs . current; rm previous; ln -fs . previous;', pty=True)
deploy()
def deploy():
"""
Deploy the latest version of the site to the servers,
install any required third party modules,
install the virtual host and then restart the webserver
"""
require('hosts', provided_by=[localhost,webserver])
require('path')
import time
env.release = time.strftime('%Y%m%d%H%M%S')
upload_tar_from_git()
install_requirements()
symlink_current_release()
migrate()
install_site()
restart_webserver()
def deploy_version(version):
"Specify a specific version to be made live"
require('hosts', provided_by=[localhost,webserver])
require('path')
env.version = version
with cd(env.path):
run('rm releases/previous; mv releases/current releases/previous;', pty=True)
run('ln -s %(version)s releases/current' % env, pty=True)
run('mkdir -p releases/current/media; mkdir -p releases/current/static/uploaded; chown g+w releases/current/static/uploaded; chown g+w releases/current/media' % env, pty=True)
restart_webserver()
def rollback():
"""
Limited rollback capability. Simple loads the previously current
version of the code. Rolling back again will swap between the two.
"""
require('hosts', provided_by=[localhost,webserver])
require('path')
with cd(env.path):
run('mv releases/current releases/_previous;', pty=True)
run('mv releases/previous releases/current;', pty=True)
run('mv releases/_previous releases/previous;', pty=True)
restart_webserver()
# Helpers. These are called by other functions rather than directly
def upload_tar_from_git():
"Create an archive from the current Git master branch and upload it"
require('release', provided_by=[deploy, setup])
local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env)
run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True)
put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env)
run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
local('rm %(release)s.tar.gz' % env)
def install_site():
"Add the virtualhost file to apache"
require('release', provided_by=[deploy, setup])
sudo('cd %(path)s/releases/current/; cp conf/%(host)s/bhagirath-vhost.conf /etc/apache2/sites-available/000-%(project_name)s' % env)
sudo('cd %(path)s/releases/current/; cp -f conf/%(host)s/wsgi.load /etc/apache2/mods-available/' % env)
#sudo('cd %(path)s/releases/current/conf/%(host)s/; cp celeryd celeryevcam celerybeat /etc/init.d/; cp celeryd-defaults /etc/default/celeryd' % env)
#sudo('/etc/init.d/celeryd restart')
#sudo('/etc/init.d/celeryevcam restart')
#sudo('/etc/init.d/celerybeat restart')
sudo('cd /etc/apache2/sites-available/; a2ensite 000-%(project_name)s' % env, pty=True)
def install_requirements():
"Install the required packages from the requirements file using pip"
require('release', provided_by=[deploy, setup])
run('cd %(path)s; export PIP_REQUIRE_VIRTUALENV=true; source bin/activate; bin/pip install -r ./releases/%(release)s/requirements.txt' % env, pty=True)
def symlink_current_release():
"Symlink our current release"
require('release', provided_by=[deploy, setup])
with cd(env.path):
run('rm releases/previous; mv releases/current releases/previous;')
run('ln -s %(release)s releases/current' % env)
def migrate():
"Update the database"
require('project_name')
sudo('cd %(path)s; chmod -R a+w shared' % env)
run('cd %(path)s/releases/current/%(project_name)s; ../../../bin/python manage.py syncdb --noinput' % env, pty=True)
run('cd %(path)s/releases/current/%(project_name)s; ../../../bin/python manage.py migrate' % env, pty=True)
run('cd %(path)s/releases/current/%(project_name)s; ../../../bin/python manage.py loaddata geographical_region_data.json' % env, pty=True)
sudo('cd %(path)s; chmod -R a+w shared' % env) # make the db readable
def restart_webserver():
"Restart the web server"
sudo('/etc/init.d/apache2 reload', pty=True)