PageRenderTime 44ms CodeModel.GetById 2ms app.highlight 36ms RepoModel.GetById 2ms app.codeStats 0ms

/silk/lib.py

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