Merge "Make Info.endpoint a config override"
diff --git a/doc/source/admin/components.rst b/doc/source/admin/components.rst
index 84ebc10..b555abc 100644
--- a/doc/source/admin/components.rst
+++ b/doc/source/admin/components.rst
@@ -618,9 +618,13 @@
 Web Server
 ----------
 
-The Zuul web server currently acts as a websocket interface to live log
-streaming. Eventually, it will serve as the single process handling all
-HTTP interactions with Zuul.
+.. TODO: Turn REST API into a link to swagger docs when we grow them
+
+The Zuul web server serves as the single process handling all HTTP
+interactions with Zuul. This includes the websocket interface for live
+log streaming, the REST API and the html/javascript dashboard. All three are
+served as a holistic web application. For information on additional supported
+deployment schemes, see :ref:`web-deployment-options`.
 
 Web servers need to be able to connect to the Gearman server (usually
 the scheduler host).  If the SQL reporter is used, they need to be
@@ -655,6 +659,11 @@
 
       Port to use for web server process.
 
+   .. attr:: rest_api_url
+
+      Base URL on which the zuul-web REST service is exposed, if different
+      than the base URL where the web application is hosted.
+
    .. attr:: websocket_url
 
       Base URL on which the websocket service is exposed, if different
diff --git a/doc/source/admin/installation.rst b/doc/source/admin/installation.rst
index ae7d571..735b315 100644
--- a/doc/source/admin/installation.rst
+++ b/doc/source/admin/installation.rst
@@ -67,3 +67,57 @@
 the correct version will be installed automatically with Zuul.
 Because of the close integration of Zuul and Ansible, attempting to
 use other versions of Ansible with Zuul is not recommended.
+
+.. _web-deployment-options:
+
+Web Deployment Options
+======================
+
+The ``zuul-web`` service provides an web dashboard, a REST API and a websocket
+log streaming service as a single holistic web application. For production use
+it is recommended to run it behind a reverse proxy, such as Apache or Nginx.
+
+More advanced users may desire to do one or more exciting things such as:
+
+White Label
+  Serve the dashboard of an individual tenant at the root of its own domain.
+  https://zuul.openstack.org is an example of a Zuul dashboard that has been
+  white labeled for the ``openstack`` tenant of its Zuul.
+
+Static Offload
+  Shift the duties of serving static files, such as HTML, Javascript, CSS or
+  images either to the Reverse Proxy server or to a completely separate
+  location such as a Swift Object Store or a CDN-enabled static web server.
+
+Sub-URL
+  Serve a Zuul dashboard from a location below the root URL as part of
+  presenting integration with other application.
+  https://softwarefactory-project.io/zuul3/ is an example of a Zuul dashboard
+  that is being served from a Sub-URL.
+
+None of those make any sense for simple non-production oriented deployments, so
+all discussion will assume that the ``zuul-web`` service is exposed via a
+Reverse Proxy. Where rewrite rule examples are given, they will be given
+with Apache syntax, but any other Reverse Proxy should work just fine.
+
+Basic Reverse Proxy
+-------------------
+
+Using Apache as the Reverse Proxy requires the ``mod_proxy``,
+``mod_proxy_http`` and ``mod_proxy_wstunnel`` modules to be installed and
+enabled. Static Offload and White Label additionally require ``mod_rewrite``.
+
+Static Offload
+--------------
+
+.. TODO: Fill in specifics in the next patch
+
+White Labeled Tenant
+--------------------
+
+.. TODO: Fill in specifics in the next patch
+
+Sub-URL
+-------
+
+.. TODO: Fill in specifics in the next patch
diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py
index 602209f..75cf8f3 100644
--- a/tests/unit/test_web.py
+++ b/tests/unit/test_web.py
@@ -254,7 +254,7 @@
         self.assertEqual(
             info, {
                 "info": {
-                    "endpoint": "http://localhost:%s" % self.port,
+                    "rest_api_url": None,
                     "capabilities": {
                         "job_history": False
                     },
@@ -275,7 +275,7 @@
         self.assertEqual(
             info, {
                 "info": {
-                    "endpoint": "http://localhost:%s" % self.port,
+                    "rest_api_url": None,
                     "tenant": "tenant-one",
                     "capabilities": {
                         "job_history": False
diff --git a/zuul/model.py b/zuul/model.py
index 44e8d06..a434834 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -3214,16 +3214,16 @@
 class WebInfo(object):
     """Information about the system needed by zuul-web /info."""
 
-    def __init__(self, websocket_url=None, endpoint=None,
+    def __init__(self, websocket_url=None, rest_api_url=None,
                  capabilities=None, stats_url=None,
                  stats_prefix=None, stats_type=None):
         self.capabilities = capabilities or Capabilities()
-        self.websocket_url = websocket_url
-        self.stats_url = stats_url
+        self.rest_api_url = rest_api_url
         self.stats_prefix = stats_prefix
         self.stats_type = stats_type
-        self.endpoint = endpoint
+        self.stats_url = stats_url
         self.tenant = None
+        self.websocket_url = websocket_url
 
     def __repr__(self):
         return '<WebInfo 0x%x capabilities=%s>' % (
@@ -3231,32 +3231,33 @@
 
     def copy(self):
         return WebInfo(
-            websocket_url=self.websocket_url,
-            endpoint=self.endpoint,
-            stats_url=self.stats_url,
+            capabilities=self.capabilities.copy(),
+            rest_api_url=self.rest_api_url,
             stats_prefix=self.stats_prefix,
             stats_type=self.stats_type,
-            capabilities=self.capabilities.copy())
+            stats_url=self.stats_url,
+            websocket_url=self.websocket_url)
 
     @staticmethod
     def fromConfig(config):
         return WebInfo(
-            websocket_url=get_default(config, 'web', 'websocket_url', None),
-            stats_url=get_default(config, 'web', 'stats_url', None),
+            rest_api_url=get_default(config, 'web', 'rest_api_url', None),
             stats_prefix=get_default(config, 'statsd', 'prefix'),
             stats_type=get_default(config, 'web', 'stats_type', 'graphite'),
+            stats_url=get_default(config, 'web', 'stats_url', None),
+            websocket_url=get_default(config, 'web', 'websocket_url', None),
         )
 
     def toDict(self):
         d = dict()
+        d['capabilities'] = self.capabilities.toDict()
+        d['rest_api_url'] = self.rest_api_url
         d['websocket_url'] = self.websocket_url
         stats = dict()
-        stats['url'] = self.stats_url
         stats['prefix'] = self.stats_prefix
         stats['type'] = self.stats_type
+        stats['url'] = self.stats_url
         d['stats'] = stats
-        d['endpoint'] = self.endpoint
-        d['capabilities'] = self.capabilities.toDict()
         if self.tenant:
             d['tenant'] = self.tenant
         return d
diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py
index 31eac7d..8f4bf72 100755
--- a/zuul/web/__init__.py
+++ b/zuul/web/__init__.py
@@ -261,19 +261,12 @@
         return await self.log_streaming_handler.processRequest(
             request)
 
-    async def _handleRootInfo(self, request):
-        info = self.info.copy()
-        info.endpoint = str(request.url.parent)
-        return self._handleInfo(info)
+    def _handleRootInfo(self, request):
+        return self._handleInfo(self.info)
 
     def _handleTenantInfo(self, request):
         info = self.info.copy()
         info.tenant = request.match_info["tenant"]
-        # yarl.URL.parent on a root url returns the root url, so this is
-        # both safe and accurate for white-labeled tenants like OpenStack,
-        # zuul-web running on / and zuul-web running on a sub-url like
-        # softwarefactory-project.io
-        info.endpoint = str(request.url.parent.parent.parent)
         return self._handleInfo(info)
 
     def _handleInfo(self, info):