/silverlining/commands/serve.py
Python | 167 lines | 142 code | 13 blank | 12 comment | 26 complexity | 1bd81b0df028322b818665cd76dae349 MD5 | raw file
1"""Serve an app locally/for development""" 2import os 3import sys 4import subprocess 5import glob 6import shlex 7import urlparse 8import urllib 9from cmdutils import CommandError 10from tempita import Template 11from paste import httpserver 12import silversupport 13from silversupport.appconfig import AppConfig 14from silversupport.shell import run 15 16 17def command_serve(config): 18 dir = os.path.abspath(config.args.dir) 19 if not os.path.exists(os.path.join(dir, 'app.ini')): 20 raise CommandError( 21 "Could not find app.ini in %s" % config.args.dir) 22 appconfig = AppConfig(os.path.join(dir, 'app.ini')) 23 appconfig.check_service_setup(config.logger) 24 if appconfig.platform == 'python': 25 serve_python(config, appconfig) 26 elif appconfig.platform == 'php': 27 serve_php(config, appconfig) 28 29 30def serve_python(config, appconfig): 31 dir = os.path.abspath(config.args.dir) 32 if os.path.exists(os.path.join(dir, 'bin', 'python')): 33 # We are in a virtualenv situation... 34 cmd = [os.path.join(dir, 'bin', 'python'), 35 os.path.abspath(os.path.join(__file__, '../../devel-runner.py')), 36 dir] 37 else: 38 cmd = [sys.executable, 39 os.path.abspath(os.path.join(__file__, '../../devel-runner.py')), 40 dir] 41 ## FIXME: should cut down the environ significantly 42 environ = os.environ.copy() 43 environ['SILVER_INSTANCE_NAME'] = 'localhost' 44 environ['SILVER_PASTE_LOCATION'] = httpserver.__file__ 45 environ['SILVER_SERVE_HOST'] = config.args.host 46 environ['SILVER_SERVE_PORT'] = config.args.port 47 if config.args.config: 48 environ['SILVER_APP_CONFIG'] = os.path.abspath(config.args.config) 49 proc = None 50 try: 51 try: 52 while 1: 53 try: 54 proc = subprocess.Popen(cmd, cwd=dir, env=environ) 55 except: 56 config.logger.warn('Error running command: %s' % ' '.join(cmd)) 57 raise 58 proc.communicate() 59 if proc.returncode == 3: 60 # Signal to do a restart 61 config.logger.notify('Restarting...') 62 else: 63 return 64 sys.exit(proc.returncode) 65 finally: 66 if (proc is not None 67 and hasattr(os, 'kill')): 68 import signal 69 try: 70 os.kill(proc.pid, signal.SIGTERM) 71 except (OSError, IOError): 72 pass 73 except KeyboardInterrupt: 74 print 'Terminating' 75 76 77def serve_php(config, appconfig): 78 apache_config_tmpl = Template.from_filename( 79 os.path.join(os.path.dirname(__file__), 'php-devel-server.conf.tmpl')) 80 path_prefixes = [os.path.join(appconfig.app_dir, 'static'), 81 appconfig.php_root] 82 if appconfig.writable_root_location != '/dev/null': 83 path_prefixes.append(appconfig.writable_root_location 84 + '/%{ENV:SILVER_HOSTNAME}') 85 path_prefixes.append(appconfig.writable_root_location) 86 tempdir = os.path.join(os.path.abspath(config.args.dir), '.apache') 87 if not os.path.exists(tempdir): 88 os.makedirs(tempdir) 89 includes = glob.glob('/etc/apache2/mods-enabled/*.load') 90 includes += glob.glob('/etc/apache2/mods-enabled/*.conf') 91 silver_secret_file = os.path.join(tempdir, 'secret.txt') 92 silver_env_vars = os.path.join(tempdir, 'silver-env-vars.php') 93 appconfig.write_php_env(silver_env_vars) 94 if not os.path.exists(silver_secret_file): 95 fp = open(silver_secret_file, 'wb') 96 fp.write('localsecret') 97 fp.close() 98 apache_config = apache_config_tmpl.substitute( 99 mgr_scripts=os.path.join(os.path.dirname(os.path.dirname(__file__)), 'mgr-scripts'), 100 path_prefixes=path_prefixes, 101 ## FIXME: better values: 102 tempdir=tempdir, 103 apache_pid_file=os.path.join(tempdir, 'apache.pid'), 104 mods_dir='/etc/apache2/mods-enabled', 105 includes=includes, 106 silver_instance_dir=os.path.abspath(config.args.dir), 107 silver_secret_file=silver_secret_file, 108 silver_env_vars=silver_env_vars, 109 silver_funcs=os.path.join(os.path.dirname(silversupport.__file__), 'php', 'functions.php'), 110 ) 111 conf_file = os.path.join(tempdir, 'apache.conf') 112 config.logger.info('Writing config to %s' % conf_file) 113 fp = open(conf_file, 'w') 114 fp.write(apache_config) 115 fp.close() 116 exe_name = search_path(['apache2', 'apache', 'httpd']) 117 config.logger.notify('Serving on http://localhost:8080') 118 ## FIXME: -X would also be an alternative to -DFOREGROUND; not sure which is better 119 ## FIXME: this logic (and call_script()) repeats stuff in devel-server.py: 120 for url in appconfig.update_fetch: 121 if url.startswith('script:'): 122 script = url[len('script:'):] 123 print 'Running update script %s' % script 124 call_script(appconfig, script) 125 else: 126 print 'Fetching update URL %s' % url 127 url = urlparse.urljoin('http://localhost:8080', url) 128 r = urllib.urlopen(url) 129 ## FIXME: handle non-200 status 130 body = r.read() 131 if body: 132 sys.stdout.write(body) 133 if not body.endswith('\n'): 134 sys.stdout.write('\n') 135 sys.stdout.flush() 136 run([exe_name, '-f', conf_file, 137 '-d', config.args.dir, '-DFOREGROUND']) 138 139 140def call_script(app_config, script): 141 run([sys.executable, os.path.join(os.path.dirname(os.path.dirname(__file__)), 142 'mgr-scripts', 'call-script.py'), 143 app_config.app_dir] + shlex.split(script)) 144 145 146def _turn_sigterm_into_systemexit(): 147 """ 148 Attempts to turn a SIGTERM exception into a SystemExit exception. 149 """ 150 try: 151 import signal 152 except ImportError: 153 return 154 155 def handle_term(signo, frame): 156 raise SystemExit 157 signal.signal(signal.SIGTERM, handle_term) 158 159 160def search_path(exe_names): 161 ## FIXME: should I allow for some general environmental variable override here? 162 paths = os.environ['PATH'].split(os.path.pathsep) 163 for name in exe_names: 164 for path in paths: 165 if os.path.exists(os.path.join(path, name)): 166 return name 167 return exe_names[0]