changeset 917:33a89139c284

serve: Add `--admin` option to run the administration panel. - Removed the `admin run` command. - Cleaned up middlewares a bit.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 29 Sep 2017 08:42:38 -0700
parents 84ce51430346
children 7f1da7e7b154
files piecrust/commands/builtin/admin.py piecrust/commands/builtin/serving.py piecrust/serving/middlewares.py piecrust/serving/server.py piecrust/serving/wrappers.py
diffstat 5 files changed, 122 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/commands/builtin/admin.py	Wed Sep 27 19:07:55 2017 -0700
+++ b/piecrust/commands/builtin/admin.py	Fri Sep 29 08:42:38 2017 -0700
@@ -1,7 +1,7 @@
 import os
 import os.path
 import logging
-from piecrust import CACHE_DIR, CONFIG_PATH
+from piecrust import CONFIG_PATH
 from piecrust.commands.base import ChefCommand
 from piecrust.pathutil import SiteNotFoundError
 
@@ -24,39 +24,6 @@
             help="Creates a new administration panel website.")
         p.set_defaults(sub_func=self._initAdminSite)
 
-        p = subparsers.add_parser(
-            'genpass',
-            help=("Generates the hashed password for use as an "
-                  "admin password"))
-        p.add_argument('password', help="The password to hash.")
-        p.set_defaults(sub_func=self._generatePassword)
-
-        p = subparsers.add_parser(
-            'run',
-            help="Runs the administrative panel website.")
-        p.add_argument(
-            '-p', '--port',
-            help="The port for the administrative panel website.",
-            default=8090)
-        p.add_argument(
-            '-a', '--address',
-            help="The host for the administrative panel website.",
-            default='localhost')
-        p.add_argument(
-            '--no-assets',
-            help="Don't process and monitor the asset folder(s).",
-            dest='monitor_assets',
-            action='store_false')
-        p.add_argument(
-            '--use-reloader',
-            help="Restart the server when PieCrust code changes",
-            action='store_true')
-        p.add_argument(
-            '--use-debugger',
-            help="Show the debugger when an error occurs",
-            action='store_true')
-        p.set_defaults(sub_func=self._runAdminSite)
-
     def checkedRun(self, ctx):
         if ctx.app.root_dir is None:
             raise SiteNotFoundError(theme=ctx.app.theme_site)
@@ -66,33 +33,6 @@
             return
         return ctx.args.sub_func(ctx)
 
-    def _runAdminSite(self, ctx):
-        # See `_run_sse_check` in `piecrust.serving.wrappers` for an
-        # explanation of this check.
-        if (ctx.args.monitor_assets and (
-                not (ctx.args.debug or ctx.args.use_reloader) or
-                os.environ.get('WERKZEUG_RUN_MAIN') == 'true')):
-            from piecrust.serving.procloop import ProcessingLoop
-            out_dir = os.path.join(
-                ctx.app.root_dir, CACHE_DIR, 'admin', 'server')
-            proc_loop = ProcessingLoop(ctx.appfactory, out_dir)
-            proc_loop.start()
-
-        es = {
-            'FOODTRUCK_CMDLINE_MODE': True,
-            'FOODTRUCK_ROOT': ctx.app.root_dir,
-            'FOODTRUCK_URL_PREFIX': '',
-            'SECRET_KEY': os.urandom(22),
-            'LOGIN_DISABLED': True}
-        if ctx.args.debug or ctx.args.use_debugger:
-            es['DEBUG'] = True
-
-        run_foodtruck(
-            host=ctx.args.address,
-            port=ctx.args.port,
-            use_reloader=ctx.args.use_reloader,
-            extra_settings=es)
-
     def _initAdminSite(self, ctx):
         import io
         import getpass
@@ -138,30 +78,3 @@
         hashpw = bcrypt.hashpw(binpw, bcrypt.gensalt()).decode('utf8')
         logger.info(hashpw)
 
-
-def run_foodtruck(host=None, port=None, use_reloader=False,
-                  extra_settings=None):
-    es = {}
-    if extra_settings:
-        es.update(extra_settings)
-
-    # Disable PIN protection with Werkzeug's debugger.
-    os.environ['WERKZEUG_DEBUG_PIN'] = 'off'
-
-    try:
-        from piecrust.admin.web import create_foodtruck_app
-        app = create_foodtruck_app(es)
-        app.run(host=host, port=port, use_reloader=use_reloader,
-                threaded=True)
-    except SystemExit:
-        # This is needed for Werkzeug's code reloader to be able to correctly
-        # shutdown the child process in order to restart it (otherwise, SSE
-        # generators will keep it alive).
-        try:
-            from . import pubutil
-            logger.debug("Shutting down SSE generators from main...")
-            pubutil.server_shutdown = True
-        except ImportError:
-            pass
-        raise
-
--- a/piecrust/commands/builtin/serving.py	Wed Sep 27 19:07:55 2017 -0700
+++ b/piecrust/commands/builtin/serving.py	Fri Sep 29 08:42:38 2017 -0700
@@ -40,28 +40,15 @@
             default='werkzeug')
 
     def run(self, ctx):
+        appfactory = ctx.appfactory
         host = ctx.args.address
         port = int(ctx.args.port)
-        debug = ctx.args.debug or ctx.args.use_debugger
-        appfactory = ctx.appfactory
-
-        if ctx.args.wsgi == 'werkzeug':
-            from piecrust.serving.wrappers import run_werkzeug_server
-            run_werkzeug_server(
-                appfactory, host, port,
-                serve_admin=ctx.args.admin,
-                use_debugger=debug,
-                use_reloader=ctx.args.use_reloader)
+        use_debugger = ctx.args.debug or ctx.args.use_debugger
 
-        elif ctx.args.wsgi == 'gunicorn':
-            from piecrust.serving.wrappers import run_gunicorn_server
-            options = {
-                'bind': '%s:%s' % (host, port),
-                'accesslog': '-',  # print access log to stderr
-            }
-            if debug:
-                options['loglevel'] = 'debug'
-            if ctx.args.use_reloader:
-                options['reload'] = True
-            run_gunicorn_server(appfactory, gunicorn_options=options)
-
+        from piecrust.serving.wrappers import run_piecrust_server
+        run_piecrust_server(
+            ctx.args.wsgi, appfactory, host, port,
+            is_cmdline_mode=True,
+            serve_admin=ctx.args.admin,
+            use_reloader=ctx.args.use_reloader,
+            use_debugger=use_debugger)
--- a/piecrust/serving/middlewares.py	Wed Sep 27 19:07:55 2017 -0700
+++ b/piecrust/serving/middlewares.py	Fri Sep 29 08:42:38 2017 -0700
@@ -12,7 +12,7 @@
     make_wrapped_file_response, get_requested_page, get_app_for_server)
 
 
-class StaticResourcesMiddleware(object):
+class PieCrustStaticResourcesMiddleware(object):
     """ WSGI middleware that serves static files from the `resources/server`
         directory in the PieCrust package.
     """
@@ -38,7 +38,8 @@
 
 
 class PieCrustDebugMiddleware(object):
-    """ WSGI middleware that handles debugging of PieCrust stuff.
+    """ WSGI middleware that handles debugging of PieCrust stuff, and runs
+        the asset pipeline in an SSE thread.
     """
     def __init__(self, app, appfactory,
                  run_sse_check=None):
--- a/piecrust/serving/server.py	Wed Sep 27 19:07:55 2017 -0700
+++ b/piecrust/serving/server.py	Fri Sep 29 08:42:38 2017 -0700
@@ -21,11 +21,11 @@
 logger = logging.getLogger(__name__)
 
 
-class WsgiServer(object):
+class PieCrustServer(object):
     """ A WSGI application that serves a PieCrust website.
     """
     def __init__(self, appfactory, **kwargs):
-        self.server = Server(appfactory, **kwargs)
+        self.server = _ServerImpl(appfactory, **kwargs)
 
     def __call__(self, environ, start_response):
         return self.server._run_request(environ, start_response)
@@ -52,7 +52,7 @@
         return desc
 
 
-class Server(object):
+class _ServerImpl(object):
     """ The PieCrust server.
     """
     def __init__(self, appfactory,
--- a/piecrust/serving/wrappers.py	Wed Sep 27 19:07:55 2017 -0700
+++ b/piecrust/serving/wrappers.py	Fri Sep 29 08:42:38 2017 -0700
@@ -6,8 +6,41 @@
 logger = logging.getLogger(__name__)
 
 
-def run_werkzeug_server(appfactory, host, port,
-                        use_debugger=False, use_reloader=False):
+def run_piecrust_server(wsgi, appfactory, host, port,
+                        is_cmdline_mode=False,
+                        serve_admin=False,
+                        use_debugger=False,
+                        use_reloader=False):
+
+    if wsgi == 'werkzeug':
+        _run_werkzeug_server(appfactory, host, port,
+                             is_cmdline_mode=is_cmdline_mode,
+                             serve_admin=serve_admin,
+                             use_debugger=use_debugger,
+                             use_reloader=use_reloader)
+
+    elif wsgi == 'gunicorn':
+        options = {
+            'bind': '%s:%s' % (host, port),
+            'accesslog': '-',  # print access log to stderr
+        }
+        if use_debugger:
+            options['loglevel'] = 'debug'
+        if use_reloader:
+            options['reload'] = True
+        _run_gunicorn_server(appfactory,
+                             is_cmdline_mode=is_cmdline_mode,
+                             gunicorn_options=options)
+
+    else:
+        raise Exception("Unknown WSGI server: %s" % wsgi)
+
+
+def _run_werkzeug_server(appfactory, host, port, *,
+                         is_cmdline_mode=False,
+                         serve_admin=False,
+                         use_debugger=False,
+                         use_reloader=False):
     from werkzeug.serving import run_simple
 
     def _run_sse_check():
@@ -21,6 +54,9 @@
                 os.environ.get('WERKZEUG_RUN_MAIN') == 'true')
 
     app = _get_piecrust_server(appfactory,
+                               is_cmdline_mode=is_cmdline_mode,
+                               serve_site=True,
+                               serve_admin=serve_admin,
                                run_sse_check=_run_sse_check)
 
     # We need to do a few things to get Werkzeug to properly shutdown or
@@ -45,6 +81,10 @@
         from piecrust.serving import procloop
         procloop.server_shutdown = True
 
+        if serve_admin:
+            from piecrust.admin import pubutil
+            pubutil.server_shutdown = True
+
     def _shutdown_server_and_raise_sigint():
         if not use_reloader or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
             # We only need to shutdown the SSE requests for the process
@@ -75,7 +115,9 @@
         raise
 
 
-def run_gunicorn_server(appfactory, gunicorn_options=None):
+def _run_gunicorn_server(appfactory,
+                         is_cmdline_mode=False,
+                         gunicorn_options=None):
     from gunicorn.app.base import BaseApplication
 
     class PieCrustGunicornApplication(BaseApplication):
@@ -92,20 +134,72 @@
         def load(self):
             return self.app
 
-    app = _get_piecrust_server(appfactory)
+    app = _get_piecrust_server(appfactory,
+                               is_cmdline_mode=is_cmdline_mode)
 
     gunicorn_options = gunicorn_options or {}
     app_wrapper = PieCrustGunicornApplication(app, gunicorn_options)
     app_wrapper.run()
 
 
-def _get_piecrust_server(appfactory, run_sse_check=None):
-    from piecrust.serving.middlewares import (
-        StaticResourcesMiddleware, PieCrustDebugMiddleware)
-    from piecrust.serving.server import WsgiServer
-    app = WsgiServer(appfactory)
-    app = StaticResourcesMiddleware(app)
-    app = PieCrustDebugMiddleware(
-        app, appfactory, run_sse_check=run_sse_check)
+def _get_piecrust_server(appfactory, *,
+                         serve_site=True,
+                         serve_admin=False,
+                         is_cmdline_mode=False,
+                         run_sse_check=None):
+    app = None
+
+    if serve_site:
+        from piecrust.serving.middlewares import (
+            PieCrustStaticResourcesMiddleware, PieCrustDebugMiddleware)
+        from piecrust.serving.server import PieCrustServer
+
+        app = PieCrustServer(appfactory)
+        app = PieCrustStaticResourcesMiddleware(app)
+
+        if is_cmdline_mode:
+            app = PieCrustDebugMiddleware(
+                app, appfactory, run_sse_check=run_sse_check)
+
+    if serve_admin:
+        from piecrust.admin.web import create_foodtruck_app
+
+        admin_root_url = '/pc-admin'
+        es = {
+            'FOODTRUCK_CMDLINE_MODE': is_cmdline_mode,
+            'FOODTRUCK_ROOT': appfactory.root_dir,
+            'FOODTRUCK_URL_PREFIX': admin_root_url,
+            'DEBUG': appfactory.debug}
+        if is_cmdline_mode:
+            es.update({
+                'SECRET_KEY': os.urandom(22),
+                'LOGIN_DISABLED': True})
+
+        if appfactory.debug and is_cmdline_mode:
+            # Disable PIN protection with Werkzeug's debugger.
+            os.environ['WERKZEUG_DEBUG_PIN'] = 'off'
+
+        admin_app = create_foodtruck_app(es)
+        admin_app.wsgi_app = _PieCrustSiteOrAdminMiddleware(
+            app, admin_app.wsgi_app, admin_root_url)
+        app = admin_app
+
     return app
 
+
+class _PieCrustSiteOrAdminMiddleware:
+    def __init__(self, main_app, admin_app, admin_root_url):
+        from werkzeug.exceptions import abort
+
+        def _err_resp(e, sr):
+            abort(404)
+
+        self.main_app = main_app
+        self.admin_app = admin_app or _err_resp
+        self.admin_root_url = admin_root_url
+
+    def __call__(self, environ, start_response):
+        path_info = environ.get('PATH_INFO', '')
+        if path_info.startswith(self.admin_root_url):
+            return self.admin_app(environ, start_response)
+        return self.main_app(environ, start_response)