Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serve external version through El Proxito #6434

Merged
merged 11 commits into from Jan 6, 2020
10 changes: 0 additions & 10 deletions docs/development/settings.rst
Expand Up @@ -180,13 +180,3 @@ project and build documentations without having elasticsearch.


.. _elasticsearch-dsl-py.connections.configure: https://elasticsearch-dsl.readthedocs.io/en/stable/configuration.html#multiple-clusters

EXTERNAL_VERSION_URL
--------------------

Default: ``None``

The URL where we host external version builds (Pull Requests).
Set this to the URL where the static files are uploaded to,
with a prefix of `/external/`,
to make it work.
16 changes: 14 additions & 2 deletions readthedocs/builds/models.py
Expand Up @@ -298,8 +298,20 @@ def get_absolute_url(self):
# but this is much simpler to handle since we only link them a couple places for now
if self.type == EXTERNAL:
# Django's static file serving doesn't automatically append index.html
url = f'{settings.EXTERNAL_VERSION_URL}/html/' \
f'{self.project.slug}/{self.slug}/index.html'
scheme = 'https' if settings.PUBLIC_DOMAIN_USES_HTTPS else 'http'
path = self.project.get_storage_path(
type_='html',
version_slug=self.slug,
version_type=self.type,
include_file=False,
)

# We don't want the `external/` part in the user-facing URL
if path.startswith(EXTERNAL):
path = path.replace(f'{EXTERNAL}/', '', 1)

domain = settings.RTD_EXTERNAL_VERSION_DOMAIN
url = f'{scheme}://{domain}/{path}/index.html'
return url

if not self.built and not self.uploaded:
Expand Down
36 changes: 36 additions & 0 deletions readthedocs/proxito/tests/test_full.py
Expand Up @@ -91,8 +91,44 @@ def test_single_version_serving_looks_like_normal(self):
resp['x-accel-redirect'], '/proxito/media/html/project/latest/en/stable/awesome.html',
)

@override_settings(
RTD_EXTERNAL_VERSION_DOMAIN='external-builds.dev.readthedocs.io',
)
def test_external_version_serving(self):
fixture.get(
Version,
verbose_name='10',
slug='10',
type=EXTERNAL,
active=True,
project=self.project,
)
url = '/html/project/10/awesome.html'
host = 'external-builds.dev.readthedocs.io'
resp = self.client.get(url, HTTP_HOST=host)
self.assertEqual(
resp['x-accel-redirect'], '/proxito/media/external/html/project/10/awesome.html',
)

# Invalid tests

@override_settings(
RTD_EXTERNAL_VERSION_DOMAIN='external-builds.dev.readthedocs.io',
)
def test_invalid_domain_for_external_version_serving(self):
fixture.get(
Version,
verbose_name='10',
slug='10',
type=EXTERNAL,
active=True,
project=self.project,
)
url = '/html/project/10/awesome.html'
host = 'project.dev.readthedocs.io'
resp = self.client.get(url, HTTP_HOST=host)
self.assertEqual(resp.status_code, 404)

def test_invalid_language_for_project_with_versions(self):
url = '/foo/latest/awesome.html'
host = 'project.dev.readthedocs.io'
Expand Down
12 changes: 12 additions & 0 deletions readthedocs/proxito/tests/test_urls.py
Expand Up @@ -105,3 +105,15 @@ def test_single_version(self):
'filename': 'some/path/index.html',
},
)

def test_external_version(self):
match = resolve('/html/project/version/path/index.html')
self.assertEqual(match.url_name, 'docs_detail_external_version')
self.assertEqual(match.args, ())
self.assertEqual(
match.kwargs, {
'project_slug': 'project',
'version_slug': 'version',
'filename': 'path/index.html',
},
)
17 changes: 17 additions & 0 deletions readthedocs/proxito/urls.py
Expand Up @@ -29,10 +29,12 @@
* Can't be translated (pip.rtfd.io/cz/en/latest/index.html)
"""

from django.conf import settings
from django.conf.urls import url
from django.views import defaults

from readthedocs.constants import pattern_opts
from readthedocs.builds.constants import EXTERNAL
from readthedocs.proxito.views.serve import (
ServeDocs,
ServeError404,
Expand Down Expand Up @@ -86,6 +88,21 @@
# name='docs_detail',
# ),

# External versions
# (RTD_EXTERNAL_VERSION_DOMAIN/html/<project-slug>/<version-slug>/<filename>)
# NOTE: requires to be before single version
url(
(
r'^html/(?P<project_slug>{project_slug})/'
humitos marked this conversation as resolved.
Show resolved Hide resolved
r'(?P<version_slug>{version_slug})/'
r'(?P<filename>{filename_slug})'.format(
**pattern_opts,
)
),
ServeDocs.as_view(version_type=EXTERNAL),
name='docs_detail_external_version',
),

# (Sub)project single version
url(
(
Expand Down
36 changes: 30 additions & 6 deletions readthedocs/proxito/views/serve.py
Expand Up @@ -14,7 +14,7 @@
from django.views import View
from django.views.decorators.cache import cache_page

from readthedocs.builds.constants import LATEST, STABLE
from readthedocs.builds.constants import LATEST, STABLE, EXTERNAL, INTERNAL
from readthedocs.builds.models import Version
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.projects import constants
Expand All @@ -32,6 +32,8 @@

class ServeDocsBase(ServeRedirectMixin, ServeDocsMixin, View):

version_type = INTERNAL

humitos marked this conversation as resolved.
Show resolved Hide resolved
def get(self,
request,
project_slug=None,
Expand All @@ -42,6 +44,16 @@ def get(self,
): # noqa
"""Take the incoming parsed URL's and figure out what file to serve."""

if all([
self.version_type == EXTERNAL,
request.get_host() != settings.RTD_EXTERNAL_VERSION_DOMAIN,
]):
log.warning(
'Trying to serve an EXTERNAL version under a not allowed '
'domain. url=%s', request.path,
)
raise Http404()

final_project, lang_slug, version_slug, filename = _get_project_data_from_request( # noqa
request,
project_slug=project_slug,
Expand All @@ -57,8 +69,12 @@ def get(self,
)

# Handle a / redirect when we aren't a single version
if all([lang_slug is None, version_slug is None, filename == '',
not final_project.single_version]):
if all([
lang_slug is None,
version_slug is None,
filename == '',
not final_project.single_version,
]):
redirect_to = redirect_project_slug(
request,
project=final_project,
Expand All @@ -70,8 +86,12 @@ def get(self,
)
return redirect_to

if (lang_slug is None or version_slug is None) and not final_project.single_version:
log.info(
if all([
(lang_slug is None or version_slug is None),
not final_project.single_version,
self.version_type != EXTERNAL,
]):
log.warning(
'Invalid URL for project with versions. url=%s, project=%s',
filename, final_project.slug
)
Expand All @@ -93,7 +113,11 @@ def get(self,
return self.get_unauthed_response(request, final_project)

storage_path = final_project.get_storage_path(
type_='html', version_slug=version_slug, include_file=False
type_='html',
version_slug=version_slug,
include_file=False,
version_type=self.version_type,

)

storage = get_storage_class(settings.RTD_BUILD_MEDIA_STORAGE)()
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/settings/base.py
Expand Up @@ -45,7 +45,7 @@ class CommunityBaseSettings(Settings):
# or use the same domain where the docs are being served
# (omit the host if that's the case).
RTD_PROXIED_API_URL = PUBLIC_API_URL
EXTERNAL_VERSION_URL = None # for pull request builds
RTD_EXTERNAL_VERSION_DOMAIN = 'external-builds.readthedocs.io'

# Doc Builder Backends
MKDOCS_BACKEND = 'readthedocs.doc_builder.backends.mkdocs'
Expand Down
2 changes: 0 additions & 2 deletions readthedocs/settings/dev.py
Expand Up @@ -32,8 +32,6 @@ def DATABASES(self): # noqa
SLUMBER_API_HOST = 'http://127.0.0.1:8000'
PUBLIC_API_URL = 'http://127.0.0.1:8000'

EXTERNAL_VERSION_URL = 'http://127.0.0.1:8000/static/external'

BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_RESULT_SERIALIZER = 'json'
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/settings/docker_compose.py
Expand Up @@ -19,6 +19,7 @@ class DockerBaseSettings(CommunityDevSettings):
PUBLIC_API_URL = 'http://community.dev.readthedocs.io'
RTD_PROXIED_API_URL = PUBLIC_API_URL
SLUMBER_API_HOST = 'http://web:8000'
RTD_EXTERNAL_VERSION_DOMAIN = 'external-builds.community.dev.readthedocs.io'

MULTIPLE_APP_SERVERS = ['web']
MULTIPLE_BUILD_SERVERS = ['build']
Expand Down Expand Up @@ -101,7 +102,6 @@ def DATABASES(self): # noqa
]
AZURE_BUILD_STORAGE_CONTAINER = 'builds'
BUILD_COLD_STORAGE_URL = 'http://storage:10000/builds'
EXTERNAL_VERSION_URL = 'http://external-builds.community.dev.readthedocs.io'
AZURE_EMULATED_MODE = True
AZURE_CUSTOM_DOMAIN = 'storage:10000'
AZURE_SSL = False