Repository: fedora-infra/bodhi

Issue:
Bodhi docs are not reachable
Bodhi documentation is not reachable (404 Error) since the datacenter move to iad2:
https://bodhi.fedoraproject.org/docs

This hasn't changed after today update.


Discussion:
Ah this is something I have broken by switching to gunicorn instead of httpd in production. httpd used to serve the docs and gunicorn cannot do that (serve static content). 

We had problems with httpd (growing memory and getting OOM killed) in openshift just before the move so the quick solution was to try to move to gunicorn instead. I guess we have 2 choice here either we create a new pod with httpd that serves only the docs or we host the docs somewhere else like readthedocs or similar.
Running a pod to serve docs seems a waste of resources to me... +1 for readthedocs.
Or better, would be possible to sync the docs we produce to something like docs.fedoraproject.org/bodhi ?
> Ah this is something I have broken by switching to gunicorn instead of httpd in production. httpd used to serve the docs and gunicorn cannot do that (serve static content).
> 
I'm not familiar with gunicorn, but I assume it sits behind some HTTP proxy (nginx?) https://gunicorn.org/index.html#deployment
Can't we serve static files from nginx?
> > Ah this is something I have broken by switching to gunicorn instead of httpd in production. httpd used to serve the docs and gunicorn cannot do that (serve static content).
> 
> I'm not familiar with gunicorn, but I assume it sits behind some HTTP proxy (nginx?) https://gunicorn.org/index.html#deployment
> Can't we serve static files from nginx?

In our case it is a bit complex :P we have the OpenShift haproxy instance that routes the traffic to the correct pod. I am not sure if we could use that to serve static content.
Before that we have the fedora infrastructure proxies that routes bodhi.fp.o to openshift. So I think it does something like browser --> httpd (fedora proxies) --> haproxy (fedora proxies) --> haproxy(openshift) --> gunicorn.

We could probably serve the docs directly from the fedora proxies
> browser --> httpd (fedora proxies) --> haproxy (fedora proxies) --> haproxy(openshift) --> gunicorn.

Wow lol. That's crazy. You probably have a 3rd choice to re-create the docs dynamically however long that takes :-)P
Pagure recently gained support to serve static file via whitenoise http://whitenoise.evans.io/en/stable/ which should make it more openshift-friendly.

This is also a library that noggin uses. Perhaps worth looking at for bodhi as well?
Yes it would be nice to have the API docs available somewhere again.

Resolving patch:
diff --git a/bodhi/server/__init__.py b/bodhi/server/__init__.py
--- a/bodhi/server/__init__.py
+++ b/bodhi/server/__init__.py
@@ -29,6 +29,7 @@
 from pyramid.tweens import EXCVIEW
 from sqlalchemy import engine_from_config, event
 from sqlalchemy.orm import scoped_session, sessionmaker
+from whitenoise import WhiteNoise
 import pkg_resources
 
 from bodhi.server import bugs, buildsys
@@ -361,4 +362,6 @@ def main(global_config, testing=None, session=None, **settings):
     Session.remove()
 
     log.info('Bodhi ready and at your service!')
-    return config.make_wsgi_app()
+    app = config.make_wsgi_app()
+    app = WhiteNoise(app, root="/usr/share/doc/bodhi-docs/html/", prefix="/docs", index_file=True)
+    return app


Test patch:
diff --git a/bodhi/tests/server/services/test_updates.py b/bodhi/tests/server/services/test_updates.py
--- a/bodhi/tests/server/services/test_updates.py
+++ b/bodhi/tests/server/services/test_updates.py
@@ -406,7 +406,8 @@ def test_koji_config_url(self, *args):
         """
         Test html rendering of default build link
         """
-        self.app.app.registry.settings['koji_web_url'] = 'https://koji.fedoraproject.org/koji/'
+        self.app.app.application.registry.settings['koji_web_url'] = \
+            'https://koji.fedoraproject.org/koji/'
         nvr = 'bodhi-2.0.0-2.fc17'
         with fml_testing.mock_sends(update_schemas.UpdateRequestTestingV1):
             resp = self.app.post_json('/updates/', self.get_update(nvr))
@@ -421,7 +422,8 @@ def test_koji_config_url_without_trailing_slash(self, *args):
         """
         Test html rendering of default build link without trailing slash
         """
-        self.app.app.registry.settings['koji_web_url'] = 'https://koji.fedoraproject.org/koji'
+        self.app.app.application.registry.settings['koji_web_url'] = \
+            'https://koji.fedoraproject.org/koji'
         nvr = 'bodhi-2.0.0-2.fc17'
         with fml_testing.mock_sends(update_schemas.UpdateRequestTestingV1):
             resp = self.app.post_json('/updates/', self.get_update(nvr))
@@ -437,7 +439,7 @@ def test_koji_config_mock_url_without_trailing_slash(self, *args):
         Test html rendering of build link using a mock config variable 'koji_web_url'
         without a trailing slash in it
         """
-        self.app.app.registry.settings['koji_web_url'] = 'https://host.org'
+        self.app.app.application.registry.settings['koji_web_url'] = 'https://host.org'
         nvr = 'bodhi-2.0.0-2.fc17'
         with fml_testing.mock_sends(update_schemas.UpdateRequestTestingV1):
             resp = self.app.post_json('/updates/', self.get_update(nvr))
@@ -1305,7 +1307,8 @@ def test_decision_context_testing(self):
     def test_home_html_legal(self):
         """Test the home page HTML when a legal link is configured."""
         with mock.patch.dict(
-                self.app.app.registry.settings, {'legal_link': 'http://loweringthebar.net/'}):
+                self.app.app.application.registry.settings,
+                {'legal_link': 'http://loweringthebar.net/'}):
             resp = self.app.get('/', headers={'Accept': 'text/html'})
 
         assert 'Fedora Updates System' in resp
@@ -1315,7 +1318,7 @@ def test_home_html_legal(self):
 
     def test_home_html_no_legal(self):
         """Test the home page HTML when no legal link is configured."""
-        with mock.patch.dict(self.app.app.registry.settings, {'legal_link': ''}):
+        with mock.patch.dict(self.app.app.application.registry.settings, {'legal_link': ''}):
             resp = self.app.get('/', headers={'Accept': 'text/html'})
 
         assert 'Fedora Updates System' in resp
@@ -1852,7 +1855,7 @@ def test_get_single_update_html_no_privacy_link(self):
         """Test getting a single update via HTML when no privacy link is configured."""
         update = Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
 
-        with mock.patch.dict(self.app.app.registry.settings, {'privacy_link': ''}):
+        with mock.patch.dict(self.app.app.application.registry.settings, {'privacy_link': ''}):
             resp = self.app.get(f'/updates/{update.alias}',
                                 headers={'Accept': 'text/html'})
 
@@ -1868,7 +1871,8 @@ def test_get_single_update_html_privacy_link(self):
         update = Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
 
         with mock.patch.dict(
-                self.app.app.registry.settings, {'privacy_link': 'https://privacyiscool.com'}):
+                self.app.app.application.registry.settings,
+                {'privacy_link': 'https://privacyiscool.com'}):
             resp = self.app.get(f'/updates/{update.alias}',
                                 headers={'Accept': 'text/html'})
 
@@ -5001,7 +5005,7 @@ def test_push_to_stable_button_not_present_when_test_gating_status_failed(self,
         update.comment(self.db, 'works', 1, 'bowlofeggs')
         # Let's clear any messages that might get sent
         self.db.info['messages'] = []
-        self.app.app.registry.settings['test_gating.required'] = True
+        self.app.app.application.registry.settings['test_gating.required'] = True
 
         resp = self.app.get(f'/updates/{update.alias}', headers={'Accept': 'text/html'})
 
@@ -5030,7 +5034,7 @@ def test_push_to_stable_button_present_when_test_gating_status_passed(self, upda
         update.comment(self.db, 'works', 1, 'bowlofeggs')
         # Let's clear any messages that might get sent
         self.db.info['messages'] = []
-        self.app.app.registry.settings['test_gating.required'] = True
+        self.app.app.application.registry.settings['test_gating.required'] = True
 
         resp = self.app.get(f'/updates/{update.alias}', headers={'Accept': 'text/html'})