fabfile.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. """ Karmaworld Fabric management script
  2. Finals Club (c) 2013"""
  3. import os
  4. import ConfigParser
  5. from cStringIO import StringIO
  6. from fabric.api import cd, lcd, prefix, run, sudo, task, local, settings
  7. from fabric.state import env as fabenv
  8. from fabric.contrib import files
  9. from dicthelpers import fallbackdict
  10. # Use local SSH config for connections if available.
  11. fabenv['use_ssh_config'] = True
  12. ######## env wrapper
  13. # global environment variables fallback to fabric env variables
  14. # (also getting vars will do format mapping on strings with env vars)
  15. env = fallbackdict(fabenv)
  16. ######### GLOBALS
  17. env.django_user = '{user}' # this will be different when sudo/django users are
  18. env.group = 'www-data'
  19. env.proj_repo = 'git@github.com:FinalsClub/karmaworld.git'
  20. env.repo_root = '/home/{django_user}/karmaworld'
  21. env.proj_root = '/var/www/karmaworld'
  22. env.branch = 'prod' # only used for supervisor conf two lines below. cleanup?
  23. env.code_root = env.proj_root
  24. env.supervisor_conf = '{code_root}/confs/{branch}/supervisord.conf'
  25. env.usde_csv = '{code_root}/confs/accreditation.csv'
  26. ######## Run Commands in Virtual Environment
  27. def virtenv_path():
  28. """
  29. Find and memoize the virtualenv for use internally.
  30. """
  31. default_venv = env.proj_root + '/venv/bin/activate'
  32. # Return environment root if its been memoized
  33. if 'env_root' in env and env['env_root']:
  34. return env['env_root']
  35. # Not memoized. Try to find a single unique virtual environment.
  36. with settings(warn_only=True):
  37. outp = run("find -L {0} -path '*/bin/activate' | grep -v '/local/'".format(env.proj_root))
  38. if not len(outp) or len(outp.splitlines()) != 1:
  39. # Cannot find any virtualenv or found multiple virtualenvs.
  40. if len(outp) and default_venv not in outp:
  41. # Multiple venvs and the default is not present.
  42. raise Exception('Cannot determine the appropriate virtualenv.')
  43. # If there are no virtualenvs, then use the default (this will create
  44. # one if being called by make_virtualenv, otherwise it will cause an
  45. # error).
  46. # If there are multiple virtualenvs and the default is in their midst,
  47. # use the default.
  48. outp = default_venv
  49. # Pop off the /bin/activate from /venv/bin/activate
  50. outp = os.path.sep.join(outp.split(os.path.sep)[:-2])
  51. env['env_root'] = outp
  52. return outp
  53. def virtenv_exec(command):
  54. """
  55. Execute command in Virtualenv
  56. """
  57. with prefix('source {0}/bin/activate'.format(virtenv_path())):
  58. run(command)
  59. ######## Sync database
  60. @task
  61. def syncdb():
  62. """
  63. Sync Database
  64. """
  65. virtenv_exec('{0}/manage.py syncdb --migrate --noinput'.format(env.code_root))
  66. ####### Collect Static Files
  67. @task
  68. def collect_static():
  69. """
  70. Collect static files (if AWS config. present, push to S3)
  71. """
  72. virtenv_exec('{0}/manage.py collectstatic --noinput'.format(env.code_root))
  73. ####### Compress Static Files
  74. @task
  75. def compress_static():
  76. """
  77. Compress static files
  78. """
  79. virtenv_exec('{0}/manage.py compress'.format(env.code_root))
  80. ####### Run Dev Server
  81. @task
  82. def dev_server():
  83. """
  84. Runs the built-in django webserver
  85. """
  86. virtenv_exec('{0}/manage.py runserver'.format(env.code_root))
  87. ####### Create Virtual Environment
  88. @task
  89. def link_code():
  90. """
  91. Link the karmaworld repo into the appropriate production location
  92. """
  93. if not files.exists(env.code_root):
  94. run('ln -s {0} {1}'.format(env.repo_root, env.code_root))
  95. @task
  96. def make_virtualenv():
  97. """
  98. Create our Virtualenv
  99. """
  100. run('virtualenv {0}'.format(virtenv_path()))
  101. @task
  102. def start_supervisord():
  103. """
  104. Starts supervisord
  105. """
  106. virtenv_exec('supervisord -c {0}'.format(env.supervisor_conf))
  107. @task
  108. def stop_supervisord():
  109. """
  110. Restarts supervisord
  111. """
  112. virtenv_exec('supervisorctl -c {0} shutdown'.format(env.supervisor_conf))
  113. @task
  114. def restart_supervisord():
  115. """
  116. Restarts supervisord, also making sure to load in new config data.
  117. """
  118. virtenv_exec('supervisorctl -c {0} update; supervisorctl -c {0} restart all'.format(env.supervisor_conf))
  119. def supervisorctl(action, process):
  120. """
  121. Takes as arguments the name of the process as is
  122. defined in supervisord.conf and the action that should
  123. be performed on it: start|stop|restart.
  124. """
  125. virtenv_exec('supervisorctl -c {0} {1} {2}'.format(env.supervisor_conf, action, process))
  126. @task
  127. def start_celery():
  128. """
  129. Starts the celeryd process
  130. """
  131. supervisorctl('start', 'celeryd')
  132. @task
  133. def stop_celery():
  134. """
  135. Stops the celeryd process
  136. """
  137. supervisorctl('stop', 'celeryd')
  138. @task
  139. def restart_celery():
  140. """
  141. Restarts the celeryd process
  142. """
  143. supervisorctl('restart', 'celeryd')
  144. @task
  145. def start_gunicorn():
  146. """
  147. Starts the gunicorn process
  148. """
  149. supervisorctl('start', 'gunicorn')
  150. @task
  151. def stop_gunicorn():
  152. """
  153. Stops the gunicorn process
  154. """
  155. supervisorctl('stop', 'gunicorn')
  156. @task
  157. def restart_gunicorn():
  158. """
  159. Restarts the gunicorn process
  160. """
  161. supervisorctl('restart', 'gunicorn')
  162. @task
  163. def flush_memcache():
  164. """
  165. Clear everything cached in memcached
  166. """
  167. virtenv_exec('echo "flush_all" | nc localhost 11211')
  168. ####### Update Requirements
  169. @task
  170. def install_reqs():
  171. # first install must be done without --upgrade for a few packages that break
  172. # due to a pip problem.
  173. virtenv_exec('pip install -r {0}/reqs/prod.txt'.format(env.code_root))
  174. @task
  175. def update_reqs():
  176. # this should generally work to install reqs too, save for a pip problem
  177. # with a few packages.
  178. virtenv_exec('pip install --upgrade -r {0}/reqs/prod.txt'.format(env.code_root))
  179. ####### Pull new code
  180. @task
  181. def update_code():
  182. virtenv_exec('cd {0}; git pull'.format(env.code_root))
  183. def backup():
  184. """
  185. Create backup using bup
  186. """
  187. pass
  188. @task
  189. def file_setup():
  190. """
  191. Deploy expected files and directories from non-apt system services.
  192. """
  193. ini_parser = ConfigParser.SafeConfigParser()
  194. # read remote data into a file like object
  195. data_flo = StringIO(run('cat {supervisor_conf}'.format(**env)))
  196. ini_parser.readfp(data_flo)
  197. for section, option in (('supervisord','logfile'),
  198. ('supervisord','pidfile'),
  199. ('unix_http_server','file'),
  200. ('program:celeryd','stdout_logfile')):
  201. if not ini_parser.has_section(section):
  202. raise Exception("Could not parse INI file {supervisor_conf}".format(**env))
  203. filepath = ini_parser.get(section, option)
  204. # generate file's directory structure if needed
  205. run('mkdir -p {0}'.format(os.path.split(filepath)[0]))
  206. # touch a file and change ownership if needed
  207. if 'log' in option and not files.exists(filepath):
  208. sudo('touch {0}'.format(filepath))
  209. sudo('chown {0}:{1} {2}'.format(env.django_user, env.group, filepath))
  210. @task
  211. def check_secrets():
  212. """
  213. Ensure secret files exist for syncdb to run.
  214. """
  215. secrets_path = env.code_root + '/karmaworld/secret'
  216. secrets_files = ('filepicker.py', 'static_s3.py', 'drive.py', 'client_secrets.json', 'drive.p12')
  217. errors = []
  218. for sfile in secrets_files:
  219. ffile = os.path.sep.join((secrets_path,sfile))
  220. if not files.exists(ffile):
  221. errors.append('{0} missing. Please add and try again.'.format(ffile))
  222. if errors:
  223. raise Exception('\n'.join(errors))
  224. @task
  225. def fetch_usde():
  226. """
  227. Download USDE accreditation school CSV.
  228. """
  229. virtenv_exec('{0}/manage.py fetch_usde_csv {1}'.format(env.code_root, env.usde_csv))
  230. @task
  231. def import_usde():
  232. """
  233. Import accreditation school CSV into the database and scrub it.
  234. """
  235. virtenv_exec('{0}/manage.py import_usde_csv {1}'.format(env.code_root, env.usde_csv))
  236. virtenv_exec('{0}/manage.py sanitize_usde_schools'.format(env.code_root))
  237. @task
  238. def first_deploy():
  239. """
  240. Sets up and deploys the project for the first time.
  241. """
  242. link_code()
  243. make_virtualenv()
  244. file_setup()
  245. check_secrets()
  246. install_reqs()
  247. syncdb()
  248. compress_static()
  249. collect_static()
  250. fetch_usde()
  251. import_usde()
  252. start_supervisord()
  253. print "You should run `manage.py createsuperuser` in the virtual environment"
  254. @task
  255. def deploy():
  256. """
  257. Deploys the latest changes
  258. """
  259. update_code()
  260. update_reqs()
  261. syncdb()
  262. compress_static()
  263. collect_static()
  264. restart_supervisord()
  265. ########## END COMMANDS