e06bddafc474d52583470b5613ac4a762b558453
[koed-quiz.git] / fabfile.py
1 from __future__ import with_statement # needed for python 2.5
2 from fabric.api import *
3 from fabric.contrib import files
4
5 import os
6
7
8 # ==========
9 # = Config =
10 # ==========
11 # Globals
12 env.project_name = 'koedquiz'
13 env.wsgi_name = env.project_name
14 env.use_south = True
15 env.virtualenv = '/usr/bin/virtualenv'
16
17 # Servers
18 def localhost():
19     """SSH to localhost (for debugging).
20
21     This will deploy to `test-deployment` in the project dir.
22
23     """
24     import os.path
25     from getpass import getuser
26
27     env.hosts = ['localhost']
28     env.user = getuser()
29     env.path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-deployment')
30     # This goes to VHost configuration
31     env.server_name = 'koedquiz.example.com'
32     env.server_admin = 'koedquiz <koedquiz@koedquiz.example.com>'
33     # /var/log/apache2/* logs
34     env.access_log = 'koedquiz.log'
35     env.error_log = 'koedquiz-errors.log'
36
37
38 # add additional servers here
39
40 def openquiz():
41     env.hosts = ['giewont.icm.edu.pl']
42     env.user = 'rczajka'
43     env.path = '/srv/koedquiz/openquiz'
44     env.server_name = 'otwartosc.nowoczesnapolska.org.pl'
45     env.server_admin = 'Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>'
46     env.access_log = 'koedquiz/openquiz.log'
47     env.error_log = 'koedquiz/openquiz-errors.log'
48     env.wsgi_name = 'openquiz'
49
50 def pdquiz():
51     env.hosts = ['giewont.icm.edu.pl']
52     env.user = 'rczajka'
53     env.path = '/srv/koedquiz/pdquiz'
54     env.server_name = 'domena.nowoczesnapolska.org.pl'
55     env.server_admin = 'Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>'
56     env.access_log = 'koedquiz/pdquiz.log'
57     env.error_log = 'koedquiz/pdquiz-errors.log'
58     env.wsgi_name = 'pdquiz'
59
60
61
62 servers = [localhost, openquiz, pdquiz]
63
64 # =========
65 # = Tasks =
66 # =========
67 def test():
68     "Run the test suite and bail out if it fails"
69     require('hosts', 'path', provided_by=servers)
70     require('python', provided_by=[find_python])
71     result = run('cd %(path)s/%(project_name)s; %(python)s manage.py test' % env)
72
73 def setup():
74     """
75     Setup a fresh virtualenv as well as a few useful directories, then run
76     a full deployment. virtualenv with pip should be already installed.
77     """
78     require('hosts', 'path', 'virtualenv', provided_by=servers)
79
80     run('mkdir -p %(path)s; cd %(path)s; %(virtualenv)s ve;' % env, pty=True)
81     run('cd %(path)s; mkdir releases; mkdir packages;' % env, pty=True)
82     run('cd %(path)s/releases; ln -s . current; ln -s . previous' % env, pty=True)
83     upload_default_localsettings()
84     deploy()
85
86 def deploy():
87     """
88     Deploy the latest version of the site to the servers,
89     install any required third party modules,
90     install the virtual host and then restart the webserver
91     """
92
93     import time
94     env.release = time.strftime('%Y-%m-%dT%H%M')
95
96     upload_tar_from_git()
97     find_python()
98     upload_wsgi_script()
99     upload_vhost_sample()
100     install_requirements()
101     copy_localsettings()
102     symlink_current_release()
103     collectstatic()
104     migrate()
105     restart_webserver()
106
107 def deploy_version(version):
108     "Specify a specific version to be made live"
109     require('hosts', 'path', provided_by=servers)
110     env.version = version
111     with cd(env.path):
112         run('rm releases/previous; mv releases/current releases/previous;', pty=True)
113         run('ln -s %(version)s releases/current' % env, pty=True)
114     restart_webserver()
115
116 def rollback():
117     """
118     Limited rollback capability. Simple loads the previously current
119     version of the code. Rolling back again will swap between the two.
120     """
121     require('hosts', 'path', provided_by=servers)
122     with cd(env.path):
123         run('mv releases/current releases/_previous;', pty=True)
124         run('mv releases/previous releases/current;', pty=True)
125         run('mv releases/_previous releases/previous;', pty=True)
126     restart_webserver()
127
128
129 # =====================================================================
130 # = Helpers. These are called by other functions rather than directly =
131 # =====================================================================
132 def upload_tar_from_git():
133     "Create an archive from the current Git branch and upload it"
134     print '>>> upload tar from git'
135     require('path', provided_by=servers)
136     require('release', provided_by=[deploy])
137     local('/bin/bash lib/git-archive-all.sh --format tar %(release)s.tar' % env)
138     local('gzip %(release)s.tar' % env)
139     run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True)
140     run('mkdir -p %(path)s/packages' % env, pty=True)
141     put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env)
142     run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True)
143     local('rm %(release)s.tar.gz' % env)
144
145 def find_python():
146     "Finds where virtualenv Python stuff is"
147     print ">>> find Python paths"
148     require('path', provided_by=servers)
149     env.python = '%(path)s/ve/bin/python' % env
150     env.pip = '%(path)s/ve/bin/pip' % env
151     env.site_packages = run('%(python)s -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"' % env)
152
153 def upload_vhost_sample():
154     "Create and upload Apache virtual host configuration sample"
155     print ">>> upload vhost sample"
156     require('path', 'project_name', 'user', provided_by=servers)
157     require('access_log', 'error_log', 'server_admin', 'server_name', provided_by=servers)
158     require('site_packages', provided_by=[find_python])
159     files.upload_template('%(project_name)s.vhost.template' % env, '%(path)s/%(project_name)s.vhost' % env, context=env)
160
161 def upload_wsgi_script():
162     "Create and upload a wsgi script sample"
163     print ">>> upload wsgi script sample"
164     require('path', 'project_name', provided_by=servers)
165     require('python', 'site_packages', provided_by=[find_python])
166     files.upload_template('%(project_name)s.wsgi.template' % env, '%(path)s/%(project_name)s.wsgi' % env, context=env)
167     run('chmod ug+x %(path)s/%(project_name)s.wsgi' % env)
168
169 def install_requirements():
170     "Install the required packages from the requirements file using pip"
171     print '>>> install requirements'
172     require('path', provided_by=servers)
173     require('release', provided_by=[deploy])
174     require('pip', provided_by=[find_python])
175     run('%(pip)s install -r %(path)s/releases/%(release)s/requirements.txt' % env, pty=True)
176
177 def secret_key():
178     """Generates a new SECRET_KEY."""
179     from random import Random
180     import string
181
182     r = Random()
183     return "".join(r.choice(string.printable) for i in range(64))
184
185 def upload_default_localsettings():
186     "Uploads localsettings.py with media paths and stuff"
187     print ">>> upload default localsettings.py"
188     require('path', provided_by=servers)
189
190     env.secret_key = secret_key()
191     files.upload_template('%(project_name)s/localsettings.py.template' % env, '%(path)s/localsettings.py' % env, context=env)
192
193 def copy_localsettings():
194     "Copy localsettings.py from root directory to release directory (if this file exists)"
195     print ">>> copy localsettings"
196     require('path', 'project_name', provided_by=servers)
197     require('release', provided_by=[deploy])
198
199     with settings(warn_only=True):
200         run('cp %(path)s/localsettings.py %(path)s/releases/%(release)s/%(project_name)s' % env)
201
202 def symlink_current_release():
203     "Symlink our current release"
204     print '>>> symlink current release'
205     require('path', provided_by=servers)
206     require('release', provided_by=[deploy])
207     with cd(env.path):
208         run('rm releases/previous; mv releases/current releases/previous')
209         run('ln -s %(release)s releases/current' % env)
210
211 def collectstatic():
212     """Runs collectstatic management command from Django staticfiles."""
213     print '>>> collectstatic'
214     require('path', 'project_name', provided_by=servers)
215     require('python', provided_by=[find_python])
216     with cd('%(path)s/releases/current/%(project_name)s' % env):
217         run('%(python)s manage.py collectstatic --noinput' % env, pty=True)
218
219 def migrate():
220     "Update the database"
221     print '>>> migrate'
222     require('path', 'project_name', provided_by=servers)
223     require('python', provided_by=[find_python])
224     with cd('%(path)s/releases/current/%(project_name)s' % env):
225         run('%(python)s manage.py syncdb --noinput' % env, pty=True)
226         if env.use_south:
227             run('%(python)s manage.py migrate' % env, pty=True)
228
229 def restart_webserver():
230     "Restart the web server"
231     print '>>> restart webserver'
232     require('path', 'project_name', provided_by=servers)
233     run('touch %(path)s/%(project_name)s.wsgi' % env)