This repository has been archived by the owner on Jan 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
fabfile.py
254 lines (201 loc) · 6.92 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
from __future__ import print_function, with_statement
import os
import json
from functools import wraps
from fabric.api import *
from fabric.colors import red, yellow, white
from fabric.contrib.console import confirm
from fabric.contrib.files import exists
__doc__ = """\
Before using this fabfile, you must create a .fabhosts in your project
directory. It is a JSON file with the following structure:
{
"_": {
"host_string": "example.com",
"path": "~/sites/project-env/project",
"repo_url": "git@github.com/bruth/project.git",
"nginx_conf_dir": "~/etc/nginx/conf.d",
"supervisor_conf_dir": "~/etc/supervisor.d"
},
"production": {},
"development": {
"path": "~/sites/project-dev-env/project"
},
"staging": {
"path": "~/sites/project-stage-env/project"
}
}
The "_" entry acts as the default/fallback for the other host
settings, so you only have to define the host-specific settings.
The below settings are required:
* `host_string` - hostname or IP address of the host server
* `path` - path to the deployed project *within* it's virtual environment
* `repo_url` - URL to project git repository
* `nginx_conf_dir` - path to host's nginx conf.d directory
* `supervisor_conf_dir` - path to host's supervisor
Note, additional settings can be defined and will be set on the `env`
object, but the above settings are required at a minimum.
"""
# A few setup steps and environment checks
curdir = os.path.dirname(os.path.abspath(__file__))
hosts_file = os.path.join(curdir, '.fabhosts')
# Check for the .fabhosts file
if not os.path.exists(hosts_file):
abort(white(__doc__))
base_settings = {
'host_string': '',
'path': '',
'repo_url': '',
'nginx_conf_dir': '',
'supervisor_conf_dir': '',
}
required_settings = ['host_string', 'path', 'repo_url',
'nginx_conf_dir', 'supervisor_conf_dir']
def get_hosts_settings():
# Load all the host settings
hosts = json.loads(open(hosts_file).read())
# Pop the default settings
default_settings = hosts.pop('_', {})
# Pre-populated defaults
for host in hosts:
base = base_settings.copy()
base.update(default_settings)
base.update(hosts[host])
hosts[host] = base
if not env.hosts:
abort(red('Error: At least one host must be specified'))
# Validate all hosts have an entry in the .hosts file
for target in env.hosts:
if target not in hosts:
abort(red('Error: No settings have been defined for the "{0}" host'.format(target)))
settings = hosts[target]
for key in required_settings:
if not settings[key]:
abort(red('Error: The setting "{0}" is not defined for "{1}" host'.format(key, target)))
return hosts
def host_context(func):
"Sets the context of the setting to the current host"
@wraps(func)
def decorator(*args, **kwargs):
hosts = get_hosts_settings()
with settings(**hosts[env.host]):
return func(*args, **kwargs)
return decorator
@host_context
def merge_commit(commit):
"Fetches the latest code and merges up the specified commit."
with cd(env.path):
run('git fetch')
if '@' in commit:
branch, commit = commit.split('@')
run('git checkout {0}'.format(branch))
run('git merge {0}'.format(commit))
@host_context
def current_commit():
with cd(env.path):
run('git log -1')
run('git status')
@host_context
def migrate(app_name=None, revision=None):
"Syncs and migrates the database using South."
cmd = ['python bin/manage.py syncdb --migrate']
if app_name:
cmd.append(app_name)
if revision:
cmd.append(revision)
verun(' '.join(cmd))
@host_context
def symlink_nginx():
"Symlinks the nginx config to the host's nginx conf directory."
with cd(env.path):
sudo('ln -sf $PWD/server/nginx/{host}.conf '
'{nginx_conf_dir}/varify-{host}.conf'.format(**env))
@host_context
def reload_nginx():
"Reloads nginx if the config test succeeds."
symlink_nginx()
if sudo('/etc/init.d/nginx configtest').succeeded:
sudo('/etc/init.d/nginx reload')
elif not confirm(yellow('nginx config test failed. continue?')):
abort('nginx config test failed. Aborting')
@host_context
def reload_supervisor():
"Re-link supervisor config and force an update to supervisor."
with cd(env.path):
sudo('ln -sf $PWD/server/supervisor/{host}.ini '
'{supervisor_conf_dir}/varify-{host}.ini'.format(**env))
run('supervisorctl update')
@host_context
def reload_wsgi():
"Gets the PID for the wsgi process and sends a HUP signal."
pid = run('supervisorctl pid varify-{host}'.format(host=env.host))
try:
int(pid)
sudo('kill -HUP {0}'.format(pid))
except (TypeError, ValueError):
pass
@host_context
def reload_memcached():
"Reloads memcached. WARNING this flushes all cache!"
sudo('/etc/init.d/memcached reload')
@host_context
def deploy(commit, force=False):
setup()
upload_settings()
mm_on()
merge_commit(commit)
install_deps(force)
migrate()
make()
reload_nginx()
reload_supervisor()
reload_wsgi()
if confirm(yellow('Reload memcached?')):
reload_memcached()
mm_off()
@host_context
def make():
"Rebuilds all static files using the Makefile."
verun('make')
@host_context
def setup():
"Sets up the initial environment."
parent, project = os.path.split(env.path)
if not exists(parent):
run('mkdir -p {0}'.format(parent))
run('virtualenv {0}'.format(parent))
with cd(parent):
if not exists(project):
run('git clone {repo_url} {project}'.format(project=project, **env))
@host_context
def upload_settings():
"Uploads the non-versioned local settings to the server."
local_path = os.path.join(curdir, 'settings/{0}.py'.format(env.host))
if os.path.exists(local_path):
remote_path = os.path.join(env.path, 'varify/conf/local_settings.py')
put(local_path, remote_path)
elif not confirm(yellow('No local settings found for host "{0}". Continue anyway?'.format(env.host))):
abort('No local settings found for host "{0}". Aborting.'.format(env.host))
@host_context
def install_deps(force=False):
"Install dependencies via pip."
if force:
verun('pip install -U -r requirements.txt')
else:
verun('pip install -r requirements.txt')
@host_context
def verun(cmd):
"Runs a command after the virtualenv is activated."
with cd(env.path):
with prefix('source ../bin/activate'):
run(cmd)
@host_context
def mm_on():
"Turns on maintenance mode."
with cd(env.path):
run('touch MAINTENANCE_MODE')
@host_context
def mm_off():
"Turns off maintenance mode."
with cd(env.path):
run('rm -f MAINTENANCE_MODE')