From 4d316ecc6cfe1221885228f8d3f4baa97aba8a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Fri, 5 Jan 2024 23:36:33 +0100 Subject: [PATCH] fix(mail): use deferred mailer to get rid of synchronous exceptions during SMTP timeout --- charts/templates/_helpers.tpl | 7 +++ charts/templates/web-deployment.yaml | 3 ++ charts/templates/web-mailer.yaml | 65 +++++++++++++++++++++++++ fiesta/fiesta/settings/__init__.py | 4 +- fiesta/fiesta/settings/notifications.py | 11 ++++- fiesta/fiesta/settings/project.py | 5 +- poetry.lock | 30 +++++++++++- pyproject.toml | 1 + 8 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 charts/templates/web-mailer.yaml diff --git a/charts/templates/_helpers.tpl b/charts/templates/_helpers.tpl index 31529ea6..19ef0b8c 100644 --- a/charts/templates/_helpers.tpl +++ b/charts/templates/_helpers.tpl @@ -60,3 +60,10 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Component label +*/}} +{{- define "fiesta.componentLabels" -}} +component: {{ . }} +{{- end }} diff --git a/charts/templates/web-deployment.yaml b/charts/templates/web-deployment.yaml index aec4c0bc..f56e4282 100644 --- a/charts/templates/web-deployment.yaml +++ b/charts/templates/web-deployment.yaml @@ -4,16 +4,19 @@ metadata: name: web labels: {{- include "fiesta.labels" . | nindent 4 }} + {{- include "fiesta.componentLabels" "web" | nindent 4 }} spec: replicas: 1 selector: matchLabels: {{- include "fiesta.selectorLabels" . | nindent 6 }} + {{- include "fiesta.componentLabels" "web" | nindent 6 }} template: metadata: labels: app: web {{- include "fiesta.selectorLabels" . | nindent 8 }} + {{- include "fiesta.componentLabels" "web" | nindent 8 }} annotations: kubectl.kubernetes.io/default-logs-container: web spec: diff --git a/charts/templates/web-mailer.yaml b/charts/templates/web-mailer.yaml new file mode 100644 index 00000000..5acb3522 --- /dev/null +++ b/charts/templates/web-mailer.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web-mailer + labels: + {{- include "fiesta.labels" . | nindent 4 }} + {{- include "fiesta.componentLabels" "mailer" | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "fiesta.selectorLabels" . | nindent 6 }} + {{- include "fiesta.componentLabels" "mailer" | nindent 6 }} + template: + metadata: + labels: + {{- include "fiesta.selectorLabels" . | nindent 8 }} + {{- include "fiesta.componentLabels" "mailer" | nindent 8 }} + annotations: + kubectl.kubernetes.io/default-logs-container: mailer + spec: + containers: + - name: mailer + image: "{{ .Values.web.repository }}:{{ .Values.web.tag | default .Chart.AppVersion }}" + command: + - /bin/sh + - -c + - ./manage.py runmailer_pg + envFrom: + - secretRef: + name: {{ .Values.web.secretName }} + - configMapRef: + name: {{ .Values.web.configName }} + +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: web-mailer-retry + labels: + {{- include "fiesta.labels" . | nindent 4 }} + {{- include "fiesta.componentLabels" "mailer-retry" | nindent 4 }} +spec: + schedule: "0,15,30,45 * * * *" # Run every 15 minutes + jobTemplate: + spec: + template: + metadata: + labels: + {{- include "fiesta.selectorLabels" . | nindent 12 }} + {{- include "fiesta.componentLabels" "mailer-retry" | nindent 12 }} + spec: + containers: + - name: mailer-retry + image: "{{ .Values.web.repository }}:{{ .Values.web.tag | default .Chart.AppVersion }}" + command: + - /bin/sh + - -c + - ./manage.py retry_deferred + envFrom: + - secretRef: + name: {{ .Values.web.secretName }} + - configMapRef: + name: {{ .Values.web.configName }} + restartPolicy: OnFailure diff --git a/fiesta/fiesta/settings/__init__.py b/fiesta/fiesta/settings/__init__.py index 7ecbe613..3ee5960a 100644 --- a/fiesta/fiesta/settings/__init__.py +++ b/fiesta/fiesta/settings/__init__.py @@ -9,7 +9,7 @@ from .db import DatabaseConfigMixin from .files import FilesConfigMixin, S3ConfigMixin from .logging import LoggingConfigMixin, SentryConfigMixin -from .notifications import SmtpMailerConfigMixin +from .notifications import DatabaseSmtpMailerConfigMixin from .project import ProjectConfigMixin from .security import SecurityConfigMixin from .templates import TemplatesConfigMixin @@ -58,7 +58,7 @@ class LocalProduction(Base): class Production( - SmtpMailerConfigMixin, + DatabaseSmtpMailerConfigMixin, S3ConfigMixin, SentryConfigMixin, Base, diff --git a/fiesta/fiesta/settings/notifications.py b/fiesta/fiesta/settings/notifications.py index 0d568e60..73e3faa5 100644 --- a/fiesta/fiesta/settings/notifications.py +++ b/fiesta/fiesta/settings/notifications.py @@ -5,7 +5,7 @@ from ._utils import BaseConfigurationProtocol -class SmtpMailerConfigMixin(BaseConfigurationProtocol): +class DatabaseSmtpMailerConfigMixin(BaseConfigurationProtocol): MAILER_PRIMARY_BACKEND = Value(default="django.core.mail.backends.smtp.EmailBackend") MAILER_PRIMARY_TIMEOUT = PositiveIntegerValue(default=10, cast=int) MAILER_PRIMARY_HOST_USE_TLS = BooleanValue(default=True) @@ -14,8 +14,15 @@ class SmtpMailerConfigMixin(BaseConfigurationProtocol): MAILER_PRIMARY_HOST_PASSWORD = SecretValue() MAILER_PRIMARY_HOST_USER = SecretValue() + # cannot use file, because pods + MAILER_USE_FILE_LOCK = BooleanValue(default=False) + + # backend used by django itself + EMAIL_BACKEND = Value(default="mailer.backend.DbBackend") + + # mailer used by db mailer to actually send emails @property - def EMAIL_BACKEND(self): + def MAILER_EMAIL_BACKEND(self): return self.MAILER_PRIMARY_BACKEND @property diff --git a/fiesta/fiesta/settings/project.py b/fiesta/fiesta/settings/project.py index 8f1988bf..099bdfce 100644 --- a/fiesta/fiesta/settings/project.py +++ b/fiesta/fiesta/settings/project.py @@ -39,7 +39,8 @@ def ALLOWED_HOSTS(self): return [f".{self.ROOT_DOMAIN}"] # overwritten by production mixins - EMAIL_BACKEND = Value(default="django.core.mail.backends.console.EmailBackend") + EMAIL_BACKEND = Value(default="mailer.backend.DbBackend") + MAILER_EMAIL_BACKEND = Value(default="django.core.mail.backends.console.EmailBackend") def DEFAULT_FROM_EMAIL(self): return f"Fiesta+ " @@ -108,6 +109,8 @@ def DEFAULT_FROM_EMAIL(self): # health checks "health_check", "health_check.contrib.migrations", + # django-mailer + "mailer", ] MIDDLEWARE = [ diff --git a/poetry.lock b/poetry.lock index 432af200..8e73673b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -814,6 +814,22 @@ files = [ {file = "django_loginas-0.3.11-py2.py3-none-any.whl", hash = "sha256:5492eb5b4eb86c05e86cd84fb1f89c9796498643dc23d19e9c9bf83f5768ba86"}, ] +[[package]] +name = "django-mailer" +version = "2.3.1" +description = "A reusable Django app for queuing the sending of email" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-mailer-2.3.1.tar.gz", hash = "sha256:eefa89b8abf3f10574a3322b08c82e1265d7b0822c4bbb9bd7b9a4110c823ac9"}, + {file = "django_mailer-2.3.1-py3-none-any.whl", hash = "sha256:d667ae8633d6b7c1e259d7142e6fbad0a4b079a528ecc6854b452d7bcb3e6aed"}, +] + +[package.dependencies] +Django = ">=2.2" +lockfile = ">=0.8" + [[package]] name = "django-model-path-converter" version = "0.1.0" @@ -1151,6 +1167,18 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + [[package]] name = "lxml" version = "4.9.4" @@ -2099,4 +2127,4 @@ anyio = ">=3.0.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "4661766f13bf56e59c4abc2fcd20512280d0dfa9890f8c2f61f1fbc9af346674" +content-hash = "866fce1557a136bfd1312a19ea6a035d2741ea49fe1c770b5263c05c8dbda406" diff --git a/pyproject.toml b/pyproject.toml index 7c381097..9b81190c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ sentry-sdk = {extras = ["django"], version = "^1.35.0"} django-admin-env-notice = "^1.0" cryptography = "41.0.7" django-admin-relation-links = "^0.2.5" +django-mailer = "^2.3.1" [tool.poetry.dev-dependencies] pre-commit = "^2.17.0"