1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
import sys
from urllib.parse import urljoin
import requests
from django.conf import settings
from django.core.cache import caches
from django.core.management.base import BaseCommand
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--doc-versions",
nargs="+",
help="Limit purge to these docs versions, if possible; otherwise, "
"purge everything (support for selective purging is cache-dependent).",
)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
doc_versions = set(options["doc_versions"] or [])
# purge Django first so Fastly doesn't immediately re-cache obsolete pages
self.purge_django_cache()
self.purge_fastly(doc_versions)
def purge_django_cache(self):
"""
If any doc versions have changed, we need to purge Django's per-site cache
(in Redis) so any downstream caches don't immediately re-cache obsolete
versions of the page.
We have a separate 'docs-pages' cache dedicated to this purpose so
other pages cached by the cache middleware aren't lost every
time the docs get rebuilt.
"""
caches["docs-pages"].clear()
def purge_fastly(self, doc_versions):
"""
Purges the Fastly surrogate key for the dev docs if that's
the only version that's changed, or the entire cache (purge_all)
if other versions have changed. Requires these settings:
* settings.FASTLY_SERVICE_URL: the full URL to the "Django Docs" Fastly
service API endpoint e.g.,
https://api.fastly.com/service/SU1Z0isxPaozGVKXdv0eY/
(a trailing slash will be added for you if you don't supply one)
* settings.FASTLY_API_KEY: your Fastly API key with "purge_all" and
"purge_select" scope for the above Django Docs service
Any errors are echoed to self.stderr even if --verbosity=0 to make
sure we get an email from cron about them if this task fails for any reason.
"""
fastly_service_url = getattr(settings, "FASTLY_SERVICE_URL", None)
fastly_api_key = getattr(settings, "FASTLY_API_KEY", None)
if not (fastly_service_url and fastly_api_key):
self.stderr.write(
"Fastly API key and/or service URL not found; can't purge cache"
)
# make sure Ansible sees this as a failure
sys.exit(1)
# Make sure fastly_service_url ends with a trailing slash;
# otherwise, urljoin() will lop off the last part of the path.
# If needed, urljoin() will remove any duplicate slashes for us.
fastly_service_url += "/"
s = requests.Session()
# make some allowance for temporary network failures for
# our .post() request below
retry = Retry(total=5, allowed_methods={"POST"}, backoff_factor=0.1)
s.mount(fastly_service_url, HTTPAdapter(max_retries=retry))
s.headers.update(
{
"Fastly-Key": fastly_api_key,
"Accept": "application/json",
}
)
if doc_versions == {"dev"}:
# If only the dev docs have changed, we can purge only the surrogate key
# we've set up for the dev docs release in Fastly. This will usually
# happen with every new commit to django main (on the next hour,
# when the cron job runs).
url = urljoin(fastly_service_url, "purge/dev-docs-key")
else:
# Otherwise, just purge everything, to keep things simple.
# This will usually only happen around a release when we
# want these pages to update as soon as possible anyways.
url = urljoin(fastly_service_url, "purge_all")
if self.verbosity >= 1:
self.stdout.write(f"Purging Fastly cache: {url}")
result = s.post(url).json()
if result.get("status") != "ok":
self.stderr.write(
f"WARNING: Fastly purge failed for URL: {url}; result={result}"
)
sys.exit(1)
|