PageRenderTime 35ms CodeModel.GetById 25ms RepoModel.GetById 2ms app.codeStats 0ms

/silverlining/commands/serve.py

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