MAX_RETENTION[_PER_PROJECT] as a setting

This commit is contained in:
Klaas van Schelven
2026-01-07 21:47:41 +01:00
parent 45add15c91
commit fc85b16567
5 changed files with 42 additions and 1 deletions

View File

@@ -67,6 +67,9 @@ DEFAULTS = {
"MAX_EMAILS_PER_MONTH": None, # None means "no limit"; for non-None values, the quota is per calendar month "MAX_EMAILS_PER_MONTH": None, # None means "no limit"; for non-None values, the quota is per calendar month
"MAX_RETENTION_PER_PROJECT_EVENT_COUNT": None, # None means "no limit"
"MAX_RETENTION_EVENT_COUNT": None, # None means "no limit"
# I don't think Sentry specifies this one, but we do: given the spec 8KiB should be enough by an order of magnitude. # I don't think Sentry specifies this one, but we do: given the spec 8KiB should be enough by an order of magnitude.
"MAX_HEADER_SIZE": 8 * _KIBIBYTE, "MAX_HEADER_SIZE": 8 * _KIBIBYTE,

View File

@@ -173,6 +173,9 @@ BUGSINK = {
"MAX_EVENTS_PER_HOUR": int(os.getenv("MAX_EVENTS_PER_HOUR", 5_000)), "MAX_EVENTS_PER_HOUR": int(os.getenv("MAX_EVENTS_PER_HOUR", 5_000)),
"MAX_EVENTS_PER_MONTH": int(os.getenv("MAX_EVENTS_PER_MONTH", 1_000_000)), "MAX_EVENTS_PER_MONTH": int(os.getenv("MAX_EVENTS_PER_MONTH", 1_000_000)),
"MAX_RETENTION_PER_PROJECT_EVENT_COUNT": int_or_none(os.getenv("MAX_RETENTION_PER_PROJECT_EVENT_COUNT", None)),
"MAX_RETENTION_EVENT_COUNT": int_or_none(os.getenv("MAX_RETENTION_EVENT_COUNT"), None),
# Settings that help with debugging and development ("why isn't Bugsink doing what I expect?") # Settings that help with debugging and development ("why isn't Bugsink doing what I expect?")
"VALIDATE_ON_DIGEST": os.getenv("VALIDATE_ON_DIGEST", "none").lower(), # other legal values are "warn" and "strict" "VALIDATE_ON_DIGEST": os.getenv("VALIDATE_ON_DIGEST", "none").lower(), # other legal values are "warn" and "strict"
"KEEP_ENVELOPES": int(os.getenv("KEEP_ENVELOPES", 0)), # keep this many in the database; 0 means "don't keep" "KEEP_ENVELOPES": int(os.getenv("KEEP_ENVELOPES", 0)), # keep this many in the database; 0 means "don't keep"

View File

@@ -53,6 +53,15 @@ def deduce_script_name(base_url):
return path if path not in (None, "", "/") else None return path if path not in (None, "", "/") else None
def int_or_none(value):
if value is None:
return None
try:
return int(value)
except (ValueError, TypeError):
return None
def eat_your_own_dogfood(sentry_dsn, **kwargs): def eat_your_own_dogfood(sentry_dsn, **kwargs):
""" """
Configures your Bugsink installation to send messages to some Bugsink-compatible installation. Configures your Bugsink installation to send messages to some Bugsink-compatible installation.

View File

@@ -111,7 +111,10 @@ BUGSINK = {
"MAX_EVENTS_PER_HOUR": 50_000_000, "MAX_EVENTS_PER_HOUR": 50_000_000,
"MAX_EVENTS_PER_MONTH": 1_000_000_000, "MAX_EVENTS_PER_MONTH": 1_000_000_000,
"MAX_EMAILS_PER_MONTH": 10, # for development: a thing to tune if you want to the the quota system # for development: things to tune if you want to the the quota system
"MAX_RETENTION_PER_PROJECT_EVENT_COUNT": None,
"MAX_RETENTION_EVENT_COUNT": None,
"MAX_EMAILS_PER_MONTH": 10,
"KEEP_ARTIFACT_BUNDLES": True, # in development: useful to preserve sourcemap uploads "KEEP_ARTIFACT_BUNDLES": True, # in development: useful to preserve sourcemap uploads

View File

@@ -3,8 +3,10 @@ from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.html import format_html from django.utils.html import format_html
from django.db.models import Sum
from bugsink.utils import assert_ from bugsink.utils import assert_
from bugsink.app_settings import get_settings
from teams.models import TeamMembership from teams.models import TeamMembership
from bsmain.utils import yesno from bsmain.utils import yesno
@@ -128,3 +130,24 @@ class ProjectForm(forms.ModelForm):
# how Django does this (but it requires JQuery) # how Django does this (but it requires JQuery)
# "alert_on_new_issue", "alert_on_regression", "alert_on_unmute" later # "alert_on_new_issue", "alert_on_regression", "alert_on_unmute" later
def clean_retention_max_event_count(self):
retention_max_event_count = self.cleaned_data['retention_max_event_count']
if get_settings().MAX_RETENTION_PER_PROJECT_EVENT_COUNT is not None:
if retention_max_event_count > get_settings().MAX_RETENTION_PER_PROJECT_EVENT_COUNT:
raise forms.ValidationError("The maximum allowed retention per project is %d events." %
get_settings().MAX_RETENTION_PER_PROJECT_EVENT_COUNT)
if get_settings().MAX_RETENTION_EVENT_COUNT is not None:
sum_of_others = Project.objects.exclude(pk=self.instance.pk).aggregate(
total=Sum('retention_max_event_count'))['total'] or 0
budget_left = get_settings().MAX_RETENTION_EVENT_COUNT - sum_of_others
if retention_max_event_count > budget_left:
raise forms.ValidationError("The maximum allowed retention for this project is %d events (based on the "
"installation-wide max of %d events)." % (
budget_left,
get_settings().MAX_RETENTION_EVENT_COUNT))
return retention_max_event_count