diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c25e97d..82e7fc5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -61,6 +61,10 @@ the test to run:: Making a Release ---------------- +Tag the release_ on GitHub, naming it in the ``v0.0.0`` format. + +.. _release: https://github.com/aclowes/yawn/releases/new + Build the frontend code, then bundle and release a source tarball. Finally, test installing it:: diff --git a/README.rst b/README.rst index 9a435d5..c697a3a 100644 --- a/README.rst +++ b/README.rst @@ -188,6 +188,8 @@ Links * Contributing_ * License_ +* `Deploying YAWN on Kubernetes via Google Container Engine`_ .. _Contributing: CONTRIBUTING.rst .. _License: LICENSE.txt +.. _Deploying YAWN on Kubernetes via Google Container Engine: https://github.com/aclowes/yawn-gke diff --git a/yawn/management/commands/webserver.py b/yawn/management/commands/webserver.py index 25b2b1d..66ee377 100644 --- a/yawn/management/commands/webserver.py +++ b/yawn/management/commands/webserver.py @@ -3,30 +3,11 @@ from gunicorn.app.base import BaseApplication from django.core.management.base import BaseCommand from django.core.wsgi import get_wsgi_application -from django.conf import settings -from whitenoise.django import DjangoWhiteNoise -from whitenoise.utils import decode_path_info - - -class DefaultFileServer(DjangoWhiteNoise): - def __call__(self, environ, start_response): - """ - Serve index.html if not /api/ or a static file - - This allows users to navigate directly to a URL like http://127.0.0.1:8000/workflows/2 - and we return index.html, and then react-router interprets the path. - """ - path = decode_path_info(environ['PATH_INFO']) - if path.startswith('/api'): - return self.application(environ, start_response) - static_file = self.files.get(path) - if static_file is None: - # serve the homepage on non-existent file - static_file = self.files.get('/index.html') - return self.serve(static_file, environ, start_response) class WSGIApplication(BaseApplication): + """A Gunicorn Application, with logic to parse config passed from the command""" + def __init__(self, options): self.options = options super().__init__() @@ -56,9 +37,7 @@ def _set_cfg(self, args): self.cfg.set(k.lower(), v) def load(self): - app = get_wsgi_application() - whitenoise = DefaultFileServer(app, settings) - return whitenoise + return get_wsgi_application() class Command(BaseCommand): diff --git a/yawn/management/tests/static/favicon.ico b/yawn/management/tests/static/favicon.ico deleted file mode 100644 index e69de29..0000000 diff --git a/yawn/management/tests/static/index.html b/yawn/management/tests/static/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/yawn/management/tests/test_webserver.py b/yawn/management/tests/test_webserver.py index d215a49..2f2ff98 100644 --- a/yawn/management/tests/test_webserver.py +++ b/yawn/management/tests/test_webserver.py @@ -1,9 +1,7 @@ -import os import sys from unittest import mock -from django.conf import settings from django.core import management from yawn.management.commands import webserver @@ -15,24 +13,3 @@ def test_webserver(mock_run): with mock.patch.object(sys, 'argv', ['yawn', 'worker', '-b', '0.0.0.0:8000']): management.call_command('webserver') assert mock_run.call_count == 1 - - -def test_static_files(): - root = os.path.join(settings.BASE_DIR, 'management/tests/static') - with mock.patch.object(settings, 'WHITENOISE_ROOT', root): - app = webserver.get_wsgi_application() - whitenoise = webserver.DefaultFileServer(app, settings) - whitenoise.application = mock.Mock() - - # an API request - whitenoise({'PATH_INFO': '/api/'}, None) - assert whitenoise.application.call_count == 1 - - # an unmatched file, goes to homepage - start_response = mock.Mock() - response = whitenoise({'PATH_INFO': '/', 'REQUEST_METHOD': 'GET'}, start_response) - assert 'index.html' in response.filelike.name - - # a real static file - response = whitenoise({'PATH_INFO': '/favicon.ico', 'REQUEST_METHOD': 'GET'}, start_response) - assert 'favicon.ico' in response.filelike.name diff --git a/yawn/settings/base.py b/yawn/settings/base.py index 2944ca9..3dfe361 100644 --- a/yawn/settings/base.py +++ b/yawn/settings/base.py @@ -47,6 +47,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', + 'yawn.utilities.middleware.DefaultFileMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', diff --git a/yawn/utilities/middleware.py b/yawn/utilities/middleware.py new file mode 100644 index 0000000..605814e --- /dev/null +++ b/yawn/utilities/middleware.py @@ -0,0 +1,23 @@ +from whitenoise.middleware import WhiteNoiseMiddleware + + +class DefaultFileMiddleware(WhiteNoiseMiddleware): + """ + Serve index.html if not /api/ or a static file + + This allows users to navigate directly to a URL like http://127.0.0.1:8000/workflows/2 + and we return index.html, and then react-router interprets the path. + """ + + def process_request(self, request): + + if request.path_info.startswith('/api'): + return # handled by Django + + static_file = self.files.get(request.path_info) + + if static_file is None: + # serve the homepage on non-existent file + static_file = self.files.get('/index.html') + + return self.serve(static_file, request) diff --git a/yawn/utilities/tests/static/index.html b/yawn/utilities/tests/static/index.html new file mode 100644 index 0000000..9015a7a --- /dev/null +++ b/yawn/utilities/tests/static/index.html @@ -0,0 +1 @@ +index diff --git a/yawn/utilities/tests/static/static.txt b/yawn/utilities/tests/static/static.txt new file mode 100644 index 0000000..7b4d4ba --- /dev/null +++ b/yawn/utilities/tests/static/static.txt @@ -0,0 +1 @@ +static diff --git a/yawn/utilities/tests/test_middleware.py b/yawn/utilities/tests/test_middleware.py new file mode 100644 index 0000000..3f53e06 --- /dev/null +++ b/yawn/utilities/tests/test_middleware.py @@ -0,0 +1,20 @@ +import os +from unittest import mock + +from django.conf import settings + + +def test_static_files(client): + root = os.path.join(settings.BASE_DIR, 'utilities/tests/static') + with mock.patch.object(settings, 'WHITENOISE_ROOT', root): + # an API request + response = client.get('/api/') + assert 'workflows' in response.data + + # an unmatched file, goes to homepage + response = client.get('/something') + assert next(response.streaming_content) == b'index\n' + + # a real static file + response = client.get('/static.txt') + assert next(response.streaming_content) == b'static\n'