Olemis Lang avatar Olemis Lang committed 4ea55ce

BH Trac #514 Multiproduct #509 : Make XmlRpcPlugin tests work with product environments . Details:

- Global only trac-admin commands listed in 'ProductAdminModule.GLOBAL_COMMANDS'
- TracStandalone authentication middleware supporting custom web bootstrap handlers
- Refactor web bootstrap handler loading code trac.web.main => trac.hooks

Comments (0)

Files changed (4)

-t509/t509_r1471368_bhmp_nodeps_bhsearch.diff
+#t509/t509_r1471368_bhmp_nodeps_bhsearch.diff
+t509/t509_r1477773_tracadmin_globalcmd_attr.diff
+t514/t514_r1477773_load_bootstrap_handler.diff
+t514/t514_r1477773_tracd_bootstrap.diff

t509/t509_r1477773_tracadmin_globalcmd_attr.diff

+# HG changeset patch
+# Parent 58af413418422bb115c62077ab44f8b414a71e5c
+BH Multiproduct #509 : Expose global-only admin commands in 'ProductAdminModule.GLOBAL_COMMANDS'
+
+diff -r 58af41341842 bloodhound_multiproduct/multiproduct/product_admin.py
+--- a/bloodhound_multiproduct/multiproduct/product_admin.py	Wed May 01 17:56:28 2013 -0500
++++ b/bloodhound_multiproduct/multiproduct/product_admin.py	Thu May 02 09:28:46 2013 -0500
+@@ -294,10 +294,11 @@
+             mgr = self.product_admincmd_mgr(args[0])
+             return mgr.complete_command(args[1:])
+ 
++    GLOBAL_COMMANDS = ('deploy', 'help', 'hotcopy', 'initenv', 'upgrade')
++
+     def _do_product_admin(self, prefix, *args):
+         mgr = self.product_admincmd_mgr(prefix)
+-        if args and args[0] in ('deploy', 'help', 'hotcopy', 'initenv', 
+-                                'upgrade'):
++        if args and args[0] in self.GLOBAL_COMMANDS :
+             raise AdminCommandError('%s command not supported for products' %
+                                     (args[0],))
+         mgr.execute_command(*args)

t514/t514_r1477773_load_bootstrap_handler.diff

+# HG changeset patch
+# Parent 5ecbf488b1edc758e0fb7ac937d29b6222edbf15
+BH Trac #514 : trac.web.main.load_bootstrap_handler
+
+diff -r 5ecbf488b1ed trac/trac/hooks.py
+--- a/trac/trac/hooks.py	Thu May 02 09:53:09 2013 -0500
++++ b/trac/trac/hooks.py	Thu May 02 15:12:00 2013 -0500
+@@ -19,6 +19,8 @@
+ import imp
+ import inspect
+ 
++import pkg_resources
++
+ from trac.config import Configuration
+ from trac.env import open_environment
+ from trac.util.concurrency import threading
+@@ -222,5 +224,25 @@
+ 
+ default_bootstrap_handler = DefaultBootstrapHandler()
+ 
++def load_bootstrap_handler(bootstrap_ep, log=None):
++    """Load handler for environment lookup and instantiation of request objects
++
++    :param bootstrap_ep: entry point specification
++    :param log: file-like object used to report errors
++    """
++    bootstrap = None
++    if bootstrap_ep:
++        try:
++            ep = pkg_resources.EntryPoint.parse('x = ' + bootstrap_ep)
++            bootstrap = ep.load(require=False)
++        except Exception, e:
++            if log:
++                log.write("[FAIL] [Trac] entry point '%s'. Reason %s" %
++                          (bootstrap_ep, repr(exception_to_unicode(e))))
++    if bootstrap is None:
++        bootstrap = default_bootstrap_handler
++    return bootstrap
++
++
+ # Recursive imports
+ from trac.web.main import send_project_index, get_environments
+diff -r 5ecbf488b1ed trac/trac/web/main.py
+--- a/trac/trac/web/main.py	Thu May 02 09:53:09 2013 -0500
++++ b/trac/trac/web/main.py	Thu May 02 15:12:00 2013 -0500
+@@ -342,13 +342,11 @@
+ 
+ _slashes_re = re.compile(r'/+')
+ 
+-def dispatch_request(environ, start_response, bootstrap=None):
++def dispatch_request(environ, start_response):
+     """Main entry point for the Trac web interface.
+ 
+     :param environ: the WSGI environment dict
+     :param start_response: the WSGI callback for starting the response
+-    :param bootstrap: handler responsible for environment lookup and
+-                      instantiating request objects
+     """
+ 
+     # SCRIPT_URL is an Apache var containing the URL before URL rewriting
+@@ -382,21 +380,11 @@
+ 
+     locale.setlocale(locale.LC_ALL, environ['trac.locale'])
+ 
+-    if bootstrap is None:
+-        bootstrap_ep = environ['trac.bootstrap_handler']
+-        if bootstrap_ep:
+-            from pkg_resources import EntryPoint
+-            try:
+-                ep = EntryPoint.parse('x = ' + bootstrap_ep)
+-                bootstrap = ep.load(require=False)
+-            except Exception, e:
+-                log = environ.get('wsgi.errors')
+-                if log:
+-                    log.write("[FAIL] [Trac] entry point '%s'. Reason %s" %
+-                              (bootstrap_ep, repr(exception_to_unicode(e))))
+-    if bootstrap is None:
+-        from trac.hooks import default_bootstrap_handler
+-        bootstrap = default_bootstrap_handler
++
++    # Load handler for environment lookup and instantiation of request objects
++    from trac.hooks import load_bootstrap_handler
++    bootstrap = load_bootstrap_handler(environ['trac.bootstrap_handler'],
++                                       environ.get('wsgi.errors'))
+ 
+     # Determine the environment
+     

t514/t514_r1477773_tracd_bootstrap.diff

+# HG changeset patch
+# Parent f24a7937a0aec1849a6b2b0f670ebf77da5b0edb
+BH Trac #514 : TracStandalone middlewares compatible with bootstrap handlers
+
+diff -r f24a7937a0ae bloodhound_multiproduct/multiproduct/hooks.py
+--- a/bloodhound_multiproduct/multiproduct/hooks.py	Thu May 02 15:12:00 2013 -0500
++++ b/bloodhound_multiproduct/multiproduct/hooks.py	Thu May 02 16:55:34 2013 -0500
+@@ -107,4 +107,5 @@
+ 
+ class ProductRequestFactory(RequestFactoryBase):
+     def create_request(self, env, environ, start_response):
+-        return ProductRequestWithSession(env, environ, start_response)
+\ No newline at end of file
++        return ProductRequestWithSession(env, environ, start_response) \
++            if env else RequestWithSession(environ, start_response)
+diff -r f24a7937a0ae trac/trac/hooks.py
+--- a/trac/trac/hooks.py	Thu May 02 15:12:00 2013 -0500
++++ b/trac/trac/hooks.py	Thu May 02 16:55:34 2013 -0500
+@@ -107,6 +107,12 @@
+         || trac.locale ||  || Target locale ||
+         || trac.base_url || TRAC_BASE_URL || Trac base URL hint ||
+ 
++        A new entry named 'trac.env_name' identifying environment SHOULD be
++        added (e.g. used by tracd to choose authentication realms). 
++        As a side-effect the WSGI environment dict (i.e. `environ`) may be
++        modified in many different ways to prepare it for subsequent
++        dispatching.
++
+         This method may handle the request (e.g. render environment index page)
+         in case environment lookup yields void results. In that case it MUST 
+         invoke WSGI `write` callable returned by `start_response` and raise 
+@@ -114,6 +120,7 @@
+ 
+         :param environ: WSGI environment dict
+         :param start_response: WSGI callback for starting the response
++        :return: environment object
+         :throws RequestDone: if the request is fully processed while loading
+                              target environment e.g. environment index page
+         :throws EnvironmentError: if it is impossible to find a way to locate
+@@ -125,6 +132,41 @@
+         """
+         raise NotImplementedError("Must override method 'open_environment'")
+ 
++    def default_probe_environment(self, environ):
++        """By default it will invoke `open_environment` and discard the
++        resulting environment object. This approach is generic but not
++        efficient. Should be overridden whenever possible. 
++        """
++        # If the expected configuration keys aren't found in the WSGI environment,
++        # try looking them up in the process environment variables
++        environ.setdefault('trac.env_path', os.getenv('TRAC_ENV'))
++        environ.setdefault('trac.env_parent_dir',
++                           os.getenv('TRAC_ENV_PARENT_DIR'))
++        environ.setdefault('trac.env_index_template',
++                           os.getenv('TRAC_ENV_INDEX_TEMPLATE'))
++        environ.setdefault('trac.template_vars',
++                           os.getenv('TRAC_TEMPLATE_VARS'))
++        environ.setdefault('trac.locale', '')
++        environ.setdefault('trac.base_url',
++                           os.getenv('TRAC_BASE_URL'))
++
++        self.open_environment(environ, 
++                              lambda status, headers: (lambda data: None))
++
++    def probe_environment(self, environ):
++        """This method is aimed at providing a lightweight version of
++        `open_environment` by solely applying upon `environ` the side effects 
++        needed to dispatch the request in environment context.
++
++        By default it will invoke `open_environment` and discard the
++        resulting environment object. Specialized versions will have the chance
++        to implement more efficient strategies in case environment
++        instantiation may be avoided. 
++
++        :return: None
++        """
++        self.default_probe_environment(environ)
++        
+     def create_request(self, env, environ, start_response):
+         """Instantiate request object used in subsequent request dispatching
+         
+@@ -148,7 +190,9 @@
+ 
+     def open_environment(self, environ, start_response):
+         env_path = environ.get('trac.env_path')
+-        if not env_path:
++        if env_path:
++            environ['trac.env_name'] = os.path.basename(env_path)
++        else:
+             env_parent_dir = environ.get('trac.env_parent_dir')
+             env_paths = environ.get('trac.env_paths')
+             if env_parent_dir or env_paths:
+@@ -163,7 +207,8 @@
+                     send_project_index(environ, start_response, env_parent_dir,
+                                        env_paths)
+                     raise RequestDone
+-    
++
++                environ['trac.env_name'] = env_name
+                 errmsg = None
+     
+                 # To make the matching patterns of request handlers work, we append
+diff -r f24a7937a0ae trac/trac/web/standalone.py
+--- a/trac/trac/web/standalone.py	Thu May 02 15:12:00 2013 -0500
++++ b/trac/trac/web/standalone.py	Thu May 02 16:55:34 2013 -0500
+@@ -27,6 +27,7 @@
+ from SocketServer import ThreadingMixIn
+ 
+ from trac import __version__ as VERSION
++from trac.hooks import load_bootstrap_handler
+ from trac.util import autoreload, daemon
+ from trac.web.auth import BasicAuthentication, DigestAuthentication
+ from trac.web.main import dispatch_request
+@@ -59,6 +60,31 @@
+         return self.application(environ, start_response)
+ 
+ 
++class BootstrapAuthenticationMiddleware(AuthenticationMiddleware):
++    """Authentication middleware for custom web bootstrap handlers
++    """
++    def __call__(self, environ, start_response):
++        bootstrap_ep = os.getenv('TRAC_BOOTSTRAP_HANDLER')
++        environ.setdefault('trac.bootstrap_handler', bootstrap_ep)
++
++        # Preserve original environ and probe dispatching
++        temp_environ = environ.copy()
++        bootstrap = load_bootstrap_handler(bootstrap_ep)
++        bootstrap.probe_environment(temp_environ)
++
++        path_info = temp_environ.get('PATH_INFO', '')
++        path_parts = filter(None, path_info.split('/'))
++        env_name = temp_environ.get('trac.env_name')
++        if path_parts and path_parts[0] == 'login' and env_name:
++            auth = self.auths.get(env_name, self.auths.get('*'))
++            if auth:
++                remote_user = auth.do_auth(environ, start_response)
++                if not remote_user:
++                    return []
++                environ['REMOTE_USER'] = remote_user
++        return self.application(environ, start_response)
++
++
+ class BasePathMiddleware(object):
+ 
+     def __init__(self, application, base_path):
+@@ -269,15 +295,19 @@
+     if parser.has_option('pidfile') and options.pidfile:
+         options.pidfile = os.path.abspath(options.pidfile)
+ 
+-    wsgi_app = TracEnvironMiddleware(dispatch_request,
+-                                     options.env_parent_dir, args,
+-                                     options.single_env)
++    wsgi_app = dispatch_request
++
+     if auths:
+         if options.single_env:
+             project_name = os.path.basename(args[0])
+-            wsgi_app = AuthenticationMiddleware(wsgi_app, auths, project_name)
++            wsgi_app = BootstrapAuthenticationMiddleware(wsgi_app, auths, project_name)
+         else:
+-            wsgi_app = AuthenticationMiddleware(wsgi_app, auths)
++            wsgi_app = BootstrapAuthenticationMiddleware(wsgi_app, auths)
++
++    wsgi_app = TracEnvironMiddleware(wsgi_app,
++                                     options.env_parent_dir, args,
++                                     options.single_env)
++
+     base_path = options.base_path.strip('/')
+     if base_path:
+         wsgi_app = BasePathMiddleware(wsgi_app, base_path)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.