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

feat: Added support for CDN cache purging #26930

Merged
merged 3 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -354,6 +361,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 @@ -365,6 +374,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", ""))
Loading