Files
zulip/zerver/storage.py
Tim Abbott e2ee1951e0 storage: Fix static files storage reuse issues.
Zulip's previous model for managing static asset files via Django
pipeline had some broken behavior around upgrades.  In particular, it
was for some reason storing the information as to which static files
should be used in a memcached cache that was shared between different
deployments of Zulip.  This means that during the upgrade process,
some clients might be served a version of the static assets that does
not correspond to the server they were connected to.

We've replaced that model with using ManifestStaticFilesStorage, which
instead allows each Zulip deployment directory to have its own
complete copy of the mapping of files to static assets, as it should
be.

We have to do a little bit of hackery with the staticfiles.json path
to make this work, basically because Django expects staticfiles.json
to be under STATIC_ROOT (aka the path nginx is serving to users), but
doing that doesn't really make sense for Zulip, since that directory
is shared between different deployments.
2017-02-06 16:10:24 -08:00

100 lines
3.6 KiB
Python

from __future__ import absolute_import
import os
import shutil
from typing import List, Any, Tuple
from django.conf import settings
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from pipeline.storage import PipelineMixin
class AddHeaderMixin(object):
def post_process(self, paths, dry_run=False, **kwargs):
# type: (Dict[str, Tuple[ZulipStorage, str]], bool, **Any) -> List[Tuple[str, str, bool]]
if dry_run:
return []
with open(settings.STATIC_HEADER_FILE, 'rb') as header_file:
header = header_file.read().decode(settings.FILE_CHARSET)
# A dictionary of path to tuples of (old_path, new_path,
# processed). The return value of this method is the values
# of this dictionary
ret_dict = {}
for name in paths:
storage, path = paths[name]
if not path.startswith('min/') or not path.endswith('.css'):
ret_dict[path] = (path, path, False)
continue
# Prepend the header
with storage.open(path, 'rb') as orig_file:
orig_contents = orig_file.read().decode(settings.FILE_CHARSET)
storage.delete(path)
with storage.open(path, 'w') as new_file:
new_file.write(header + orig_contents)
ret_dict[path] = (path, path, True)
super_class = super(AddHeaderMixin, self) # type: ignore # https://github.com/JukkaL/mypy/issues/857
if hasattr(super_class, 'post_process'):
super_ret = super_class.post_process(paths, dry_run, **kwargs)
else:
super_ret = []
# Merge super class's return value with ours
for val in super_ret:
old_path, new_path, processed = val
if processed:
ret_dict[old_path] = val
return list(ret_dict.values())
class RemoveUnminifiedFilesMixin(object):
def post_process(self, paths, dry_run=False, **kwargs):
# type: (Dict[str, Tuple[ZulipStorage, str]], bool, **Any) -> List[Tuple[str, str, bool]]
if dry_run:
return []
root = settings.STATIC_ROOT
to_remove = ['templates', 'styles', 'js']
for tree in to_remove:
shutil.rmtree(os.path.join(root, tree))
is_valid = lambda p: all([not p.startswith(k) for k in to_remove])
paths = {k: v for k, v in paths.items() if is_valid(k)}
super_class = super(RemoveUnminifiedFilesMixin, self) # type: ignore # https://github.com/JukkaL/mypy/issues/857
if hasattr(super_class, 'post_process'):
return super_class.post_process(paths, dry_run, **kwargs)
return []
if settings.PRODUCTION:
# This is a hack to use staticfiles.json from within the
# deployment, rather than a directory under STATIC_ROOT. By doing
# so, we can use a different copy of staticfiles.json for each
# deployment, which ensures that we always use the correct static
# assets for each deployment.
ManifestStaticFilesStorage.manifest_name = os.path.join(settings.DEPLOY_ROOT,
"staticfiles.json")
orig_path = ManifestStaticFilesStorage.path
def path(self, name):
# type: (Any, str) -> str
if name == ManifestStaticFilesStorage.manifest_name:
return name
return orig_path(self, name)
ManifestStaticFilesStorage.path = path
class ZulipStorage(PipelineMixin,
AddHeaderMixin, RemoveUnminifiedFilesMixin,
ManifestStaticFilesStorage):
pass