Skip to content

Commit 76e0d3d

Browse files
authored
Merge pull request #7812 from readthedocs/humitos/docker-env-s3-storage
Use S3 (MinIO emulator) as storage backend
2 parents fb2ac0c + 39af6e8 commit 76e0d3d

File tree

10 files changed

+162
-143
lines changed

10 files changed

+162
-143
lines changed

docker-compose.override.yml

-26
Original file line numberDiff line numberDiff line change
@@ -70,29 +70,3 @@ services:
7070
environment:
7171
- DJANGO_SETTINGS_MODULE=readthedocs.settings.build_docker
7272
- CELERY_APP_NAME=readthedocs
73-
74-
storage:
75-
# https://github.com/Azure/Azurite
76-
# Pin the version of Azurite as there are sometimes backwards incompatible changes
77-
image: mcr.microsoft.com/azure-storage/azurite:3.5.0
78-
volumes:
79-
- storage_data:/data
80-
ports:
81-
- "10000:10000"
82-
networks:
83-
readthedocs:
84-
command: ["azurite-blob", "--silent", "--blobPort", "10000", "--blobHost", "0.0.0.0", "--location", "/data", "--loose"]
85-
86-
azure-cli:
87-
image: microsoft/azure-cli
88-
volumes:
89-
- ${PWD}/common/dockerfiles/scripts/create-containers.sh:/usr/local/bin/create-containers.sh
90-
environment:
91-
- INIT=${INIT:-}
92-
links:
93-
- storage
94-
depends_on:
95-
- storage
96-
networks:
97-
readthedocs:
98-
command: ["/usr/local/bin/create-containers.sh"]

dockerfiles/nginx/proxito.conf

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ server {
55

66
# TODO: serve the favicon.ico from the project/version itself instead
77
location /favicon.ico {
8-
proxy_pass http://storage:10000/devstoreaccount1/static/images/favicon.ico;
8+
proxy_pass http://storage:9000/static/images/favicon.ico;
99
break;
1010
}
1111

@@ -34,9 +34,9 @@ server {
3434
location /proxito/ {
3535
internal;
3636
# Nginx will strip the `/proxito/` and pass just the `$storage/$type/$proj/$ver/$filename`
37-
proxy_pass http://storage:10000/;
37+
proxy_pass http://storage:9000/;
3838

39-
proxy_set_header Host storage:10000;
39+
proxy_set_header Host storage:9000;
4040
proxy_set_header X-Real-IP $remote_addr;
4141
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
4242
proxy_set_header X-Forwarded-Host $host;

dockerfiles/nginx/web.conf

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ server {
55
server_name community.dev.readthedocs.io;
66

77
location /favicon.ico {
8-
proxy_pass http://storage:10000/devstoreaccount1/static/images/favicon.ico;
8+
proxy_pass http://storage:9000/static/images/favicon.ico;
9+
break;
10+
}
11+
12+
location /static/ {
13+
proxy_pass http://storage:9000/static/;
914
break;
1015
}
1116

dockerfiles/settings/celery.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33

44
class CeleryDevSettings(DockerBaseSettings):
5-
# Since we can't properly set CORS on Azurite container
6-
# (see https://github.com/Azure/Azurite/issues/55#issuecomment-503380561)
5+
# TODO: review this since it may not be needed with MinIO (S3). For now,
6+
# this is still required, but the CORS issue may have disappeared in MinIO.
7+
8+
# Since we can't properly set CORS on storage container
79
# trying to fetch ``objects.inv`` from celery container fails because the
810
# URL is like http://community.dev.readthedocs.io/... and it should be
9-
# http://storage:10000/... This setting fixes that.
11+
# http://storage:9000/... This setting fixes that.
1012
# Once we can use CORS, we should define this setting in the
1113
# ``docker_compose.py`` file instead.
12-
AZURE_MEDIA_STORAGE_HOSTNAME = 'storage:10000'
14+
S3_MEDIA_STORAGE_OVERRIDE_HOSTNAME = 'storage:9000'
1315

1416

1517
CeleryDevSettings.load_settings(__name__)

docs/development/standards.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ After cloning ``readthedocs.org`` repository, you need to
106106

107107
inv docker.up --init # --init is only needed the first time
108108

109+
#. go to http://localhost:9000/ (MinIO S3 storage backend), click "..." and then "Edit Policy" and give "Read Only" access on all the buckets (``static`` and ``media``).
110+
109111
#. go to http://community.dev.readthedocs.io to access your local instance of Read the Docs.
110112

111113

@@ -122,8 +124,7 @@ save some work while typing docker compose commands. This section explains these
122124
Starts all the containers needed to run Read the Docs completely.
123125

124126
* ``--no-search`` can be passed to disable search
125-
* ``--init`` is used the first time this command is ran to run initial migrations, create an admin user,
126-
setup Azurite containers, etc
127+
* ``--init`` is used the first time this command is ran to run initial migrations, create an admin user, etc
127128
* ``--no-reload`` makes all celery processes and django runserver
128129
to use no reload and do not watch for files changes
129130

readthedocs/settings/docker_compose.py

+43-27
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ class DockerBaseSettings(CommunityDevSettings):
1818
PRODUCTION_DOMAIN = 'community.dev.readthedocs.io'
1919
PUBLIC_DOMAIN = 'community.dev.readthedocs.io'
2020
PUBLIC_API_URL = f'http://{PRODUCTION_DOMAIN}'
21+
2122
SLUMBER_API_HOST = 'http://web:8000'
23+
2224
RTD_EXTERNAL_VERSION_DOMAIN = 'org.dev.readthedocs.build'
2325

24-
STATIC_URL = '/devstoreaccount1/static/'
26+
STATIC_URL = '/static/'
2527

2628
# In the local docker environment, nginx should be trusted to set the host correctly
2729
USE_X_FORWARDED_HOST = True
@@ -64,8 +66,21 @@ def RTD_EXT_THEME_DEV_SERVER(self):
6466
def LOGGING(self):
6567
logging = super().LOGGING
6668
logging['loggers'].update({
67-
# Disable azurite logging
68-
'azure.storage.common': {
69+
# Disable S3 logging
70+
'boto3': {
71+
'handlers': ['null'],
72+
'propagate': False,
73+
},
74+
'botocore': {
75+
'handlers': ['null'],
76+
'propagate': False,
77+
},
78+
's3transfer': {
79+
'handlers': ['null'],
80+
'propagate': False,
81+
},
82+
# Disable Docker API logging
83+
'urllib3': {
6984
'handlers': ['null'],
7085
'propagate': False,
7186
},
@@ -115,37 +130,38 @@ def show_debug_toolbar(request):
115130

116131
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
117132

118-
# https://github.com/Azure/Azurite/blob/master/README.md#default-storage-account
119-
AZURE_ACCOUNT_NAME = 'devstoreaccount1'
120-
AZURE_ACCOUNT_KEY = 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=='
121-
AZURE_CONTAINER = 'static'
122-
AZURE_STATIC_STORAGE_CONTAINER = AZURE_CONTAINER
123-
124-
# We want to replace files for the same version built
125-
AZURE_OVERWRITE_FILES = True
126-
127-
# Storage backend for build media artifacts (PDF, HTML, ePub, etc.)
128-
RTD_BUILD_MEDIA_STORAGE = 'readthedocs.storage.azure_storage.AzureBuildMediaStorage'
129-
AZURE_STATIC_STORAGE_HOSTNAME = 'assets.community.dev.readthedocs.io:10000'
130-
131-
RTD_SAVE_BUILD_COMMANDS_TO_STORAGE = True
132-
RTD_BUILD_COMMANDS_STORAGE = 'readthedocs.storage.azure_storage.AzureBuildStorage'
133-
133+
RTD_BUILD_MEDIA_STORAGE = 'readthedocs.storage.s3_storage.S3BuildMediaStorage'
134134
# Storage backend for build cached environments
135-
RTD_BUILD_ENVIRONMENT_STORAGE = 'readthedocs.storage.azure_storage.AzureBuildEnvironmentStorage'
136-
135+
RTD_BUILD_ENVIRONMENT_STORAGE = 'readthedocs.storage.s3_storage.S3BuildEnvironmentStorage'
137136
# Storage for static files (those collected with `collectstatic`)
138-
STATICFILES_STORAGE = 'readthedocs.storage.azure_storage.AzureStaticStorage'
137+
STATICFILES_STORAGE = 'readthedocs.storage.s3_storage.S3StaticStorage'
138+
139+
AWS_ACCESS_KEY_ID = 'admin'
140+
AWS_SECRET_ACCESS_KEY = 'password'
141+
S3_MEDIA_STORAGE_BUCKET = 'media'
142+
S3_BUILD_COMMANDS_STORAGE_BUCKET = 'builds'
143+
S3_BUILD_ENVIRONMENT_STORAGE_BUCKET = 'envs'
144+
S3_STATIC_STORAGE_BUCKET = 'static'
145+
S3_STATIC_STORAGE_OVERRIDE_HOSTNAME = 'community.dev.readthedocs.io'
146+
S3_MEDIA_STORAGE_OVERRIDE_HOSTNAME = 'community.dev.readthedocs.io'
147+
148+
AWS_AUTO_CREATE_BUCKET = True
149+
AWS_DEFAULT_ACL = 'public-read'
150+
AWS_BUCKET_ACL = 'public-read'
151+
AWS_S3_ENCRYPTION = False
152+
AWS_S3_SECURE_URLS = False
153+
AWS_S3_USE_SSL = False
154+
AWS_S3_ENDPOINT_URL = 'http://storage:9000/'
155+
AWS_QUERYSTRING_AUTH = False
156+
157+
RTD_SAVE_BUILD_COMMANDS_TO_STORAGE = True
158+
RTD_BUILD_COMMANDS_STORAGE = 'readthedocs.storage.s3_storage.S3BuildCommandsStorage'
159+
BUILD_COLD_STORAGE_URL = 'http://storage:9000/builds'
139160

140161
STATICFILES_DIRS = [
141162
os.path.join(CommunityDevSettings.SITE_ROOT, 'readthedocs', 'static'),
142163
os.path.join(CommunityDevSettings.SITE_ROOT, 'media'),
143164
]
144-
AZURE_BUILD_COMMANDS_STORAGE_CONTAINER = 'builds'
145-
BUILD_COLD_STORAGE_URL = 'http://storage:10000/builds'
146-
AZURE_EMULATED_MODE = True
147-
AZURE_CUSTOM_DOMAIN = 'storage:10000'
148-
AZURE_SSL = False
149165

150166
# Remove the checks on the number of fields being submitted
151167
# This limit is mostly hit on large forms in the Django admin

readthedocs/storage/azure_storage.py

-77
This file was deleted.

readthedocs/storage/s3_storage.py

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
AWS S3 Storage backends.
3+
4+
We override the backends provided by django-storages to add some small pieces
5+
that we need to make our project to work as we want. For example, using
6+
ManifestFilesMixin for static files and OverrideHostnameMixin to make it work
7+
in our Docker Development environment.
8+
"""
9+
10+
# Disable abstract method because we are not overriding all the methods
11+
# pylint: disable=abstract-method
12+
from django.conf import settings
13+
from django.contrib.staticfiles.storage import ManifestFilesMixin
14+
from django.core.exceptions import ImproperlyConfigured
15+
from storages.backends.s3boto3 import S3Boto3Storage
16+
17+
from readthedocs.builds.storage import BuildMediaStorageMixin
18+
from readthedocs.storage.mixins import OverrideHostnameMixin
19+
20+
21+
class S3BuildMediaStorage(BuildMediaStorageMixin, OverrideHostnameMixin, S3Boto3Storage):
22+
23+
"""An AWS S3 Storage backend for build artifacts."""
24+
25+
bucket_name = getattr(settings, 'S3_MEDIA_STORAGE_BUCKET')
26+
override_hostname = getattr(settings, 'S3_MEDIA_STORAGE_OVERRIDE_HOSTNAME', None)
27+
28+
def __init__(self, *args, **kwargs):
29+
super().__init__(*args, **kwargs)
30+
31+
if not self.bucket_name:
32+
raise ImproperlyConfigured(
33+
'AWS S3 not configured correctly. '
34+
'Ensure S3_MEDIA_STORAGE_BUCKET is defined.',
35+
)
36+
37+
38+
class S3BuildCommandsStorage(S3Boto3Storage):
39+
40+
"""An AWS S3 Storage backend for build commands."""
41+
42+
bucket_name = getattr(settings, 'S3_BUILD_COMMANDS_STORAGE_BUCKET', None)
43+
44+
def __init__(self, *args, **kwargs):
45+
super().__init__(*args, **kwargs)
46+
47+
if not self.bucket_name:
48+
raise ImproperlyConfigured(
49+
'AWS S3 not configured correctly. '
50+
'Ensure S3_BUILD_COMMANDS_STORAGE_BUCKET is defined.',
51+
)
52+
53+
54+
class S3StaticStorage(OverrideHostnameMixin, ManifestFilesMixin, S3Boto3Storage):
55+
56+
"""
57+
An AWS S3 Storage backend for static media.
58+
59+
* Uses Django's ManifestFilesMixin to have unique file paths (eg. core.a6f5e2c.css)
60+
"""
61+
62+
bucket_name = getattr(settings, 'S3_STATIC_STORAGE_BUCKET')
63+
override_hostname = getattr(settings, 'S3_STATIC_STORAGE_OVERRIDE_HOSTNAME', None)
64+
65+
def __init__(self, *args, **kwargs):
66+
super().__init__(*args, **kwargs)
67+
68+
if not self.bucket_name:
69+
raise ImproperlyConfigured(
70+
'AWS S3 not configured correctly. '
71+
'Ensure S3_STATIC_STORAGE_BUCKET is defined.',
72+
)
73+
74+
self.bucket_acl = 'public-read'
75+
self.default_acl = 'public-read'
76+
self.querystring_auth = False
77+
78+
79+
class S3BuildEnvironmentStorage(BuildMediaStorageMixin, S3Boto3Storage):
80+
81+
bucket_name = getattr(settings, 'S3_BUILD_ENVIRONMENT_STORAGE_BUCKET')
82+
83+
def __init__(self, *args, **kwargs):
84+
super().__init__(*args, **kwargs)
85+
86+
if not self.bucket_name:
87+
raise ImproperlyConfigured(
88+
'AWS S3 not configured correctly. '
89+
'Ensure S3_BUILD_ENVIRONMENT_STORAGE_BUCKET is defined.',
90+
)

requirements/pip.txt

+10-2
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,19 @@ django-cors-middleware==1.4.0 # pyup: ignore
101101
# User agent parsing - used for analytics purposes
102102
user-agents==2.2.0
103103

104+
104105
# Utilities used to upload build media to cloud storage
105106
# django-storages is pinned to this particular commit because it
106107
# supports generating URLs with other method than GET when using
107-
# private buckets
108-
git+https://github.com/jschneier/django-storages@d0f027c98a877f75615cfc42b4d51c038fa41bf6#egg=django-storages[azure]==1.9.1
108+
# private buckets.
109+
#
110+
# Besides, support for the corresponding AWS_BUCKET_ACL and
111+
# AWS_AUTO_CREATE_BUCKET settings have been removed in 1.10. We depend on this
112+
# in our Docker setup. We can upgrade it but we need to add a
113+
# `create_buckets.sh` to be called on `--init` as we used to do for Azurite
114+
# https://github.com/jschneier/django-storages/pull/636
115+
git+https://github.com/jschneier/django-storages@d0f027c98a877f75615cfc42b4d51c038fa41bf6#egg=django-storages[boto3]==1.9.1
116+
109117

110118
# Required only in development and linting
111119
django-debug-toolbar==3.2

0 commit comments

Comments
 (0)