Skip to content

Commit

Permalink
feat: Added support for CDN cache purging (#26930)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Dec 16, 2024
1 parent 114936e commit 0c0087f
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 0 deletions.
40 changes: 40 additions & 0 deletions posthog/models/remote_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.http import HttpRequest
from django.utils import timezone
from prometheus_client import Counter
import requests
from sentry_sdk import capture_exception
import structlog

Expand Down Expand Up @@ -38,6 +39,12 @@
labelnames=["result"],
)

REMOTE_CONFIG_CDN_PURGE_COUNTER = Counter(
"posthog_remote_config_cdn_purge",
"Number of times the remote config CDN purge task has been run",
labelnames=["result"],
)


logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -355,6 +362,8 @@ def sync(self):

cache.set(cache_key_for_team_token(self.team.api_token, "config"), config, timeout=CACHE_TIMEOUT)

self._purge_cdn()

# TODO: Invalidate caches - in particular this will be the Cloudflare CDN cache
self.synced_at = timezone.now()
self.save()
Expand All @@ -366,6 +375,37 @@ def sync(self):
CELERY_TASK_REMOTE_CONFIG_SYNC.labels(result="failure").inc()
raise

def _purge_cdn(self):
if (
not settings.REMOTE_CONFIG_CDN_PURGE_ENDPOINT
or not settings.REMOTE_CONFIG_CDN_PURGE_TOKEN
or not settings.REMOTE_CONFIG_CDN_PURGE_DOMAINS
):
return

logger.info(f"Purging CDN for team {self.team_id}")

data: dict[str, Any] = {"files": []}

for domain in settings.REMOTE_CONFIG_CDN_PURGE_DOMAINS:
# Check if the domain starts with https:// and if not add it
full_domain = domain if domain.startswith("https://") else f"https://{domain}"
data["files"].append({"url": f"{full_domain}/array/{self.team.api_token}/config"})
data["files"].append({"url": f"{full_domain}/array/{self.team.api_token}/config.js"})
data["files"].append({"url": f"{full_domain}/array/{self.team.api_token}/array.js"})

try:
requests.post(
settings.REMOTE_CONFIG_CDN_PURGE_ENDPOINT,
headers={"Authorization": f"Bearer {settings.REMOTE_CONFIG_CDN_PURGE_TOKEN}"},
data=data,
)
except Exception:
logger.exception(f"Failed to purge CDN for team {self.team_id}")
REMOTE_CONFIG_CDN_PURGE_COUNTER.labels(result="failure").inc()
else:
REMOTE_CONFIG_CDN_PURGE_COUNTER.labels(result="success").inc()

def __str__(self):
return f"RemoteConfig {self.team_id}"

Expand Down
23 changes: 23 additions & 0 deletions posthog/models/test/test_remote_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,29 @@ def test_only_includes_recording_for_approved_domains(self):
config = self.remote_config.get_config_via_token(self.team.api_token, request=mock_request)
assert not config["sessionRecording"]

@patch("posthog.models.remote_config.requests.post")
def test_purges_cdn_cache_on_sync(self, mock_post):
with self.settings(
REMOTE_CONFIG_CDN_PURGE_ENDPOINT="https://api.cloudflare.com/client/v4/zones/MY_ZONE_ID/purge_cache",
REMOTE_CONFIG_CDN_PURGE_TOKEN="MY_TOKEN",
REMOTE_CONFIG_CDN_PURGE_DOMAINS=["cdn.posthog.com", "https://cdn2.posthog.com"],
):
self.remote_config.sync()
mock_post.assert_called_once_with(
"https://api.cloudflare.com/client/v4/zones/MY_ZONE_ID/purge_cache",
headers={"Authorization": "Bearer MY_TOKEN"},
data={
"files": [
{"url": "https://cdn.posthog.com/array/phc_12345/config"},
{"url": "https://cdn.posthog.com/array/phc_12345/config.js"},
{"url": "https://cdn.posthog.com/array/phc_12345/array.js"},
{"url": "https://cdn2.posthog.com/array/phc_12345/config"},
{"url": "https://cdn2.posthog.com/array/phc_12345/config.js"},
{"url": "https://cdn2.posthog.com/array/phc_12345/array.js"},
]
},
)


class TestRemoteConfigJS(_RemoteConfigBase):
def test_renders_js_including_config(self):
Expand Down
5 changes: 5 additions & 0 deletions posthog/settings/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,8 @@

# disables frontend side navigation hooks to make hot-reload work seamlessly
DEV_DISABLE_NAVIGATION_HOOKS = get_from_env("DEV_DISABLE_NAVIGATION_HOOKS", False, type_cast=bool)


REMOTE_CONFIG_CDN_PURGE_ENDPOINT = get_from_env("REMOTE_CONFIG_CDN_PURGE_ENDPOINT", "")
REMOTE_CONFIG_CDN_PURGE_TOKEN = get_from_env("REMOTE_CONFIG_CDN_PURGE_TOKEN", "")
REMOTE_CONFIG_CDN_PURGE_DOMAINS = get_list(os.getenv("REMOTE_CONFIG_CDN_PURGE_DOMAINS", ""))

0 comments on commit 0c0087f

Please sign in to comment.