This repository has been archived by the owner on Dec 10, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfabfile.py
293 lines (218 loc) · 8.08 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
from contextlib import nested
from datetime import datetime
import os
import random
import StringIO
import dj_database_url
from fabric.api import (
cd, env, execute, get, local, put, require, run, settings, shell_env,
task
)
from fabric.context_managers import quiet
from fabric.operations import prompt
from gitric import api as gitric
# This is the definition of your environments. Every item of the ENVIRONMENTS
# dict will be made available as a fabric task and the properties you put in a
# particular environment will be made available in the `env` variable.
ENVIRONMENTS = {
'prod': {
'root': '/var/www/code_annotate/prod/',
'hosts': ['root@myhost'],
# You can set settings that will be automatically deployed when running
# the `bootstrap` command
# 'settings': {
# 'ALLOWED_HOSTS': 'www.myhost.com',
# }
},
'dev': {
'root': '/var/www/code_annotate/staging/',
'hosts': ['root@myhost'],
# You can set settings that will be automatically deployed when running
# the `bootstrap` command
# 'settings': {
# 'ALLOWED_HOSTS': 'www.myhost.com',
# }
}
}
env.project_name = 'code_annotate'
def git_push(commit):
"""
Push the current tree to the remote server and reset the remote git
repository to the given commit. The commit can be any git object, be it a
hash, a tag or a branch.
"""
gitric.git_seed(get_project_root(), commit)
gitric.git_reset(get_project_root(), 'master')
def get_project_root():
"""
Return the path to the root of the project on the remote server.
"""
return os.path.join(env.root, env.project_name)
def get_virtualenv_root():
"""
Return the path to the virtual environment on the remote server.
"""
return os.path.join(env.root, 'venv')
def run_in_virtualenv(cmd, args):
"""
Run the given command from the remote virtualenv.
"""
return run('%s %s' % (os.path.join(get_virtualenv_root(), 'bin', cmd),
args))
def run_pip(args):
"""
Run the pip command in the remote virtualenv.
"""
return run_in_virtualenv('pip', args)
def run_python(args):
"""
Run the python command in the remote virtualenv.
"""
return run_in_virtualenv('python', args)
def install_requirements():
"""
Install the requirements from the base.txt file to the remote virtualenv.
"""
with cd(get_project_root()):
run_pip("install -r requirements/base.txt")
def migrate_database():
with cd(get_project_root()):
run_python("manage.py migrate")
def collect_static():
"""
Collect static files to the STATIC_ROOT directory.
"""
with cd(get_project_root()):
run_python("manage.py collectstatic --noinput")
def restart_process():
"""
Restart the WSGI process by touching the wsgi.py file.
"""
run('touch %s' % os.path.join(get_project_root(), env.project_name,
'wsgi.py'))
def generate_secret_key():
"""
Generate a random secret key, suitable to be used as a SECRET_KEY setting.
"""
return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
def create_structure():
"""
Create the basic directory structure on the remote server.
"""
run('mkdir -p %s' % env.root)
with cd(env.root):
run('mkdir -p static backups')
run('virtualenv venv')
def set_setting(setting_key, value=None, description=None):
"""
Sets the given setting to the given value on the remote server. If the
value is not provided, the user will be prompted for it.
TODO: use the description parameter to display a help text.
"""
if value is None:
value = prompt("Please provide value for setting %s: " % setting_key)
with cd(os.path.join(get_project_root(), 'envdir')):
put(StringIO.StringIO(value), setting_key)
@task
def bootstrap():
"""
Deploy the project for the first time. This will create the directory
structure, push the project and set the basic settings.
This task needs to be called alongside an environment task, eg. ``fab prod
bootstrap``.
"""
create_structure()
execute(git_push, commit='master')
required_settings = set(['DATABASE_URL', 'MEDIA_ROOT', 'STATIC_ROOT',
'MEDIA_URL', 'STATIC_URL', 'ALLOWED_HOSTS'])
if hasattr(env, 'settings'):
for setting, value in env.settings.items():
set_setting(setting, value=value)
# Ask for settings that are required but were not set in the parameters
# file
for setting in required_settings - set(env.settings.keys()):
set_setting(setting)
set_setting('DJANGO_SETTINGS_MODULE',
value='%s.settings.base' % env.project_name)
set_setting('SECRET_KEY', value=generate_secret_key())
execute(install_requirements)
execute(collect_static)
execute(migrate_database)
execute(restart_process)
@task
def deploy(tag):
require('root', 'project_name')
execute(git_push, commit=tag)
execute(install_requirements)
execute(collect_static)
execute(migrate_database)
execute(restart_process)
@task
def fetch_db(destination='.'):
"""
Dump the database on the remote host and retrieve it locally.
The destination parameter controls where the dump should be stored locally.
"""
require('root')
with nested(cd(get_project_root()), quiet()):
db_credentials = run('cat envdir/DATABASE_URL')
db_credentials_dict = dj_database_url.parse(db_credentials)
if not is_supported_db_engine(db_credentials_dict['ENGINE']):
raise NotImplementedError(
"The fetch_db task doesn't support the remote database engine"
)
outfile = datetime.now().strftime('%Y-%m-%d_%H%M%S.sql.gz')
outfile_remote = os.path.join('~', outfile)
with shell_env(PGPASSWORD=db_credentials_dict['PASSWORD'].replace('$', '\$')):
run('pg_dump -O -x -h {host} -U {user} {db}|gzip > {outfile}'.format(
host=db_credentials_dict['HOST'],
user=db_credentials_dict['USER'],
db=db_credentials_dict['NAME'],
outfile=outfile_remote))
get(outfile_remote, destination)
run('rm %s' % outfile_remote)
return outfile
@task
def import_db(dump_file=None):
"""
Restore the given database dump.
The dump must be a gzipped SQL dump. If the dump_file parameter is not set,
the database will be dumped and retrieved from the remote host.
"""
with open('envdir/DATABASE_URL', 'r') as db_credentials_file:
db_credentials = db_credentials_file.read()
db_credentials_dict = dj_database_url.parse(db_credentials)
if not is_supported_db_engine(db_credentials_dict['ENGINE']):
raise NotImplementedError(
"The import_db task doesn't support your database engine"
)
if dump_file is None:
dump_file = fetch_db()
db_info = {
'host': db_credentials_dict['HOST'],
'user': db_credentials_dict['USER'],
'db': db_credentials_dict['NAME'],
'db_dump': dump_file
}
with shell_env(PGPASSWORD=db_credentials_dict['PASSWORD']):
with settings(warn_only=True):
local('dropdb -h {host} -U {user} {db}'.format(**db_info))
local('createdb -h {host} -U {user} {db}'.format(**db_info))
local('gunzip -c {db_dump}|psql -h {host} -U {user} {db}'.format(
**db_info
))
def is_supported_db_engine(engine):
return engine == 'django.db.backends.postgresql_psycopg2'
# Environment handling stuff
############################
def get_environment_func(key, value):
def load_environment():
env.update(value)
env.environment = key
load_environment.__name__ = key
load_environment.__doc__ = "Definition of the %s environment." % key
return load_environment
def load_environments(environments):
for (key, values) in environments.items():
globals()[key] = task(get_environment_func(key, values))
load_environments(ENVIRONMENTS)