/silk/lib.py

https://bitbucket.org/btubbs/silk-deployment/ · Python · 207 lines · 192 code · 6 blank · 9 comment · 5 complexity · 5c8bd4d4d5e27815083aebad57cd927e MD5 · raw file

  1. import copy
  2. import os
  3. import pkg_resources
  4. import subprocess
  5. import sys
  6. import time
  7. import shutil
  8. import hashlib
  9. from signal import SIGTERM, SIGINT
  10. import app_container
  11. from app_container import *
  12. # Format for the 'bind' string we'll be making later, sticking the site name +
  13. # timestamp in the wildcard part. Used here for generating the gunicorn cmd,
  14. # and in the fabfile for plugging into the nginx config.
  15. GUNICORN_BIND_PATTERN = 'unix:/tmp/%s.sock'
  16. def get_gunicorn_cmd(site_env, bin_dir=''):
  17. """Given a copy of Fabric's state in site_env, configure and return the
  18. gunicorn cmd line needed to run the site"""
  19. site_config = site_env['config']
  20. GUNICORN_DEFAULTS = {
  21. 'workers': 1,
  22. 'log-level': 'info',
  23. 'name': 'gunicorn',
  24. 'debug': 'false',
  25. }
  26. # Make a copy here because we're going to be modifying this dict, and we
  27. # don't want to mess it up for later functions, or later runs of this
  28. # function if we're going to deploy to more than one host.
  29. gconfig = copy.copy(site_config.get('gunicorn', GUNICORN_DEFAULTS))
  30. # Default to using a unix socket for nginx->gunicorn
  31. if 'deployment' in site_env:
  32. default_bind = GUNICORN_BIND_PATTERN % site_env.deployment
  33. # Default to using the site name and timestamp in the procname
  34. gconfig['name'] = site_env['deployment']
  35. else:
  36. default_bind = 'localhost:8000'
  37. gconfig['name'] = site_config['site']
  38. gconfig['bind'] = gconfig.get('bind', default_bind)
  39. debug = gconfig.pop('debug', None)
  40. options = ' '.join(['--%s %s' % (x, y) for x, y in gconfig.iteritems()])
  41. if debug:
  42. options += ' --debug'
  43. gconfig['options'] = options
  44. gconfig['bin_dir'] = bin_dir
  45. gconfig.update(site_config)
  46. cmd = 'gunicorn %(options)s %(wsgi_app)s' % gconfig
  47. if bin_dir:
  48. cmd = '%s/%s' % (bin_dir, cmd)
  49. return cmd
  50. def get_root(start_dir):
  51. testfile = os.path.join(start_dir, 'site.yaml')
  52. if os.path.isfile(testfile):
  53. return start_dir
  54. else:
  55. parent_dir = os.path.split(start_dir)[0]
  56. if parent_dir != start_dir:
  57. return get_root(parent_dir)
  58. else:
  59. return None
  60. def get_template_path(template, root=None):
  61. """
  62. Returns path of template from site conf_templates dir, if found there, else
  63. returns template path from silk's conf_templates dir.
  64. """
  65. if root:
  66. localpath=os.path.join(root, 'conf_templates', template)
  67. if os.path.isfile(localpath):
  68. return localpath
  69. pkgpath=pkg_resources.resource_filename('silk', 'conf_templates/%s' % template)
  70. if os.path.isfile(pkgpath):
  71. return pkgpath
  72. else:
  73. raise Exception("Template not found: %s" % template)
  74. def get_rendered_template(template_name, context):
  75. """
  76. Returns text of named template, with keyword substitutions pulled from
  77. 'context'
  78. """
  79. template_path = get_template_path(template_name)
  80. txt = open(template_path, 'r').read()
  81. return txt % context
  82. def _run(args, kill_signal, cwd=os.getcwd(), env={}):
  83. env.update(os.environ)
  84. proc = subprocess.Popen(args, cwd=cwd, env=env)
  85. try:
  86. proc.wait()
  87. except KeyboardInterrupt as e:
  88. print "KeyboardInterrupt"
  89. proc.send_signal(kill_signal)
  90. except Exception as e:
  91. print e
  92. proc.send_signal(kill_signal)
  93. def run_fab(args):
  94. args[0] = 'fab'
  95. _run(args, SIGTERM)
  96. def run_devserver():
  97. # Overwrite the wsgi_app config var to point to our internal app that will
  98. # also mount the static dirs.
  99. root = os.environ['SILK_ROOT']
  100. role = os.environ['SILK_ROLE']
  101. config = app_container.get_config(root, role)
  102. config['wsgi_app'] = 'silk.devserver:app'
  103. cmd = get_gunicorn_cmd({'config': config})
  104. subproc_env = {
  105. 'SILK_ROOT': root,
  106. 'SILK_ROLE': app_container.get_role(),
  107. }
  108. # By adding our current subproc_environment to that used for the subprocess, we
  109. # ensure that the same paths will be used (such as those set by virtualenv)
  110. subproc_env.update(os.environ)
  111. _run(cmd.split(), SIGINT, cwd=root, env=subproc_env)
  112. # This 1 second sleep lets the gunicorn workers exit before we show the
  113. # prompt again.
  114. time.sleep(1)
  115. def install_skel(sitename):
  116. """Copies the contents of site_templates into the named directory (within cwd)"""
  117. root = os.environ['SILK_ROOT']
  118. #get the dir from pkg_resources
  119. src = pkg_resources.resource_filename('silk', 'site_templates')
  120. try:
  121. shutil.copytree(src, os.path.join(os.getcwd(), sitename))
  122. except OSError, e:
  123. print e
  124. def get_local_archive_dir():
  125. return os.path.join(os.path.expanduser('~'), '.silk')
  126. def get_pybundle_name(reqs):
  127. """Hash the requirements list to create a pybundle name."""
  128. # Strip leading and trailing whitespace
  129. reqs = reqs.strip()
  130. # put the lines in order to ensure consistent hashing
  131. lines = reqs.split()
  132. lines.sort()
  133. reqs = '\n'.join(lines)
  134. hash = hashlib.md5(reqs).hexdigest()
  135. return "%s.pybundle" % hash
  136. def get_pybundle_path(reqs):
  137. """Return the name of the pybundle file that corresponds to the passed-in
  138. requirements text."""
  139. return os.path.join(get_local_archive_dir(), get_pybundle_name(reqs))
  140. cmd_map = {
  141. 'run': run_devserver,
  142. 'skel': install_skel,
  143. }
  144. def cmd_dispatcher():
  145. """wraps 'fab', handles 'silk run'"""
  146. args = sys.argv
  147. try:
  148. cmd = args[1]
  149. # If a command is provided by cmd_map, use that. Else pass through to
  150. # fabric.
  151. if cmd in cmd_map:
  152. # Stick some information about the role and root into the current env,
  153. # then call the local function in cmd_map.
  154. os.environ['SILK_ROLE'] = app_container.get_role() or ''
  155. os.environ['SILK_ROOT'] = app_container.get_site_root(os.getcwd()) or ''
  156. cmd_map[cmd]()
  157. else:
  158. # Use project-provided fabfile if present, else the one built into
  159. # Silk. We'll have to trust that the project file imports ours.
  160. root = get_root(os.getcwd())
  161. site_fab = os.path.join(root, 'fabfile.py')
  162. if os.path.isfile(site_fab):
  163. fabfile = site_fab
  164. else:
  165. fabfile = pkg_resources.resource_filename('silk', 'fabfile.py')
  166. args.extend(['--fabfile', fabfile])
  167. run_fab(args)
  168. except IndexError:
  169. # Print out help text. Currently just prints it for the cmds specified
  170. # in the fabfile, which isn't great because it omits things like 'silk
  171. # run' and 'silk deps'. Would be better to inspect the fabfile and
  172. # list the cmds/docstrings myself, right along the non-fabfile cmds
  173. # that Silk provides. Or I could just make all the things I provide as
  174. # fab cmds. That might be the simpler approach.
  175. run_fab(['fab', '-l'])