fabfile.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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. ####### Run Dev Server
  74. @task
  75. def dev_server():
  76. """
  77. Runs the built-in django webserver
  78. """
  79. virtenv_exec('{0}/manage.py runserver'.format(env.code_root))
  80. ####### Create Virtual Environment
  81. @task
  82. def link_code():
  83. """
  84. Link the karmaworld repo into the appropriate production location
  85. """
  86. if not files.exists(env.code_root):
  87. run('ln -s {0} {1}'.format(env.repo_root, env.code_root))
  88. @task
  89. def make_virtualenv():
  90. """
  91. Create our Virtualenv
  92. """
  93. run('virtualenv {0}'.format(virtenv_path()))
  94. @task
  95. def start_supervisord():
  96. """
  97. Starts supervisord
  98. """
  99. virtenv_exec('supervisord -c {0}'.format(env.supervisor_conf))
  100. @task
  101. def stop_supervisord():
  102. """
  103. Restarts supervisord
  104. """
  105. virtenv_exec('supervisorctl -c {0} shutdown'.format(env.supervisor_conf))
  106. @task
  107. def restart_supervisord():
  108. """
  109. Restarts supervisord, also making sure to load in new config data.
  110. """
  111. virtenv_exec('supervisorctl -c {0} update; supervisorctl -c {0} restart all'.format(env.supervisor_conf))
  112. def supervisorctl(action, process):
  113. """
  114. Takes as arguments the name of the process as is
  115. defined in supervisord.conf and the action that should
  116. be performed on it: start|stop|restart.
  117. """
  118. virtenv_exec('supervisorctl -c {0} {1} {2}'.format(env.supervisor_conf, action, process))
  119. @task
  120. def start_celery():
  121. """
  122. Starts the celeryd process
  123. """
  124. supervisorctl('start', 'celeryd')
  125. @task
  126. def stop_celery():
  127. """
  128. Stops the celeryd process
  129. """
  130. supervisorctl('stop', 'celeryd')
  131. @task
  132. def restart_celery():
  133. """
  134. Restarts the celeryd process
  135. """
  136. supervisorctl('restart', 'celeryd')
  137. @task
  138. def start_gunicorn():
  139. """
  140. Starts the gunicorn process
  141. """
  142. supervisorctl('start', 'gunicorn')
  143. @task
  144. def stop_gunicorn():
  145. """
  146. Stops the gunicorn process
  147. """
  148. supervisorctl('stop', 'gunicorn')
  149. @task
  150. def restart_gunicorn():
  151. """
  152. Restarts the gunicorn process
  153. """
  154. supervisorctl('restart', 'gunicorn')
  155. ####### Update Requirements
  156. @task
  157. def install_reqs():
  158. # first install must be done without --upgrade for a few packages that break
  159. # due to a pip problem.
  160. virtenv_exec('pip install -r {0}/reqs/prod.txt'.format(env.code_root))
  161. @task
  162. def update_reqs():
  163. # this should generally work to install reqs too, save for a pip problem
  164. # with a few packages.
  165. virtenv_exec('pip install --upgrade -r {0}/reqs/prod.txt'.format(env.code_root))
  166. ####### Pull new code
  167. @task
  168. def update_code():
  169. virtenv_exec('cd {0}; git pull'.format(env.code_root))
  170. def backup():
  171. """
  172. Create backup using bup
  173. """
  174. pass
  175. @task
  176. def file_setup():
  177. """
  178. Deploy expected files and directories from non-apt system services.
  179. """
  180. ini_parser = ConfigParser.SafeConfigParser()
  181. # read remote data into a file like object
  182. data_flo = StringIO(run('cat {supervisor_conf}'.format(**env)))
  183. ini_parser.readfp(data_flo)
  184. for section, option in (('supervisord','logfile'),
  185. ('supervisord','pidfile'),
  186. ('unix_http_server','file'),
  187. ('program:celeryd','stdout_logfile')):
  188. if not ini_parser.has_section(section):
  189. raise Exception("Could not parse INI file {supervisor_conf}".format(**env))
  190. filepath = ini_parser.get(section, option)
  191. # generate file's directory structure if needed
  192. run('mkdir -p {0}'.format(os.path.split(filepath)[0]))
  193. # touch a file and change ownership if needed
  194. if 'log' in option and not files.exists(filepath):
  195. sudo('touch {0}'.format(filepath))
  196. sudo('chown {0}:{1} {2}'.format(env.django_user, env.group, filepath))
  197. @task
  198. def check_secrets():
  199. """
  200. Ensure secret files exist for syncdb to run.
  201. """
  202. secrets_path = env.code_root + '/karmaworld/secret'
  203. secrets_files = ('filepicker.py', 'static_s3.py', 'db_settings.py', 'drive.py', 'client_secrets.json', 'drive.p12')
  204. errors = []
  205. for sfile in secrets_files:
  206. ffile = os.path.sep.join((secrets_path,sfile))
  207. if not files.exists(ffile):
  208. errors.append('{0} missing. Please add and try again.'.format(ffile))
  209. if errors:
  210. raise Exception('\n'.join(errors))
  211. @task
  212. def fetch_usde():
  213. """
  214. Download USDE accreditation school CSV.
  215. """
  216. virtenv_exec('{0}/manage.py fetch_usde_csv {1}'.format(env.code_root, env.usde_csv))
  217. @task
  218. def import_usde():
  219. """
  220. Import accreditation school CSV into the database and scrub it.
  221. """
  222. virtenv_exec('{0}/manage.py import_usde_csv {1}'.format(env.code_root, env.usde_csv))
  223. virtenv_exec('{0}/manage.py sanitize_usde_schools'.format(env.code_root))
  224. @task
  225. def first_deploy():
  226. """
  227. Sets up and deploys the project for the first time.
  228. """
  229. link_code()
  230. make_virtualenv()
  231. file_setup()
  232. check_secrets()
  233. install_reqs()
  234. syncdb()
  235. collect_static()
  236. fetch_usde()
  237. import_usde()
  238. start_supervisord()
  239. print "You should run `manage.py createsuperuser` in the virtual environment"
  240. @task
  241. def deploy():
  242. """
  243. Deploys the latest changes
  244. """
  245. update_code()
  246. update_reqs()
  247. syncdb()
  248. collect_static()
  249. restart_supervisord()
  250. ########## END COMMANDS