fix: keep the nginx ingress annotations when the ingress controller is traefik

ingress_annotations() treated the two controllers as either/or: choosing traefik dropped the nginx proxy-body-size annotation, so on a cluster routed by nginx the 50 MiB upload cap silently became nginx' 1 MiB default and uploads failed with HTTP 413. An ingress annotation only acts when a controller of that family routes the ingress, so emit both annotation sets always: nginx's size and timeout annotations (plus the rewrite-target null that keeps API paths intact), and the Traefik middleware reference on top when the config says traefik. The Middleware object is created as before.

Version 1.6.2.
This commit is contained in:
Igor
2026-10-09 20:42:49 +03:00
parent 4a267c5336
commit 777fc8aa9a
4 changed files with 24 additions and 19 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# supaspin
Version 1.6.1
Version 1.6.2
Create, list, show and delete throwaway [Supabase](https://supabase.com) instances on a
single-node [microk8s](https://microk8s.io) server, from a terminal menu.
+1 -1
View File
@@ -1 +1 @@
1.6.1
1.6.2
+14 -11
View File
@@ -39,7 +39,7 @@ import tty
import yaml
VERSION = "1.6.1"
VERSION = "1.6.2"
CONFIG_PATH = os.environ.get("SUPASPIN_CONFIG", "/etc/supaspin/config.yaml")
STATE_DIR = os.environ.get("SUPASPIN_STATE", "/var/lib/supaspin")
CHART_REPO = "https://supabase-community.github.io/supabase-kubernetes"
@@ -189,24 +189,27 @@ def buffering_middleware(n):
def ingress_annotations(cfg, n):
"""Ingress annotations for instance n, shaped for the configured ingress controller.
With Traefik the upload cap lives in a Middleware object created next to the instance
and the ingress points at it; with nginx it is proxy-body-size, and the chart's own
rewrite-target default must be nullified or every API path breaks. The cert-manager
annotation orders a certificate from the supaspin-letsencrypt issuer; without it the
TLS secret keeps whatever supaspin put there (a saved or a self-signed certificate)."""
"""Ingress annotations for instance n.
Both controllers' annotations are always present: an annotation only takes effect
when a controller of that family actually routes the ingress, so on a Traefik
cluster the nginx keys sit inert (and vice versa) and nothing needs reconfiguring
when the router changes. The upload cap therefore lives in two places that agree:
proxy-body-size for nginx, and the Middleware object the traefik annotation points
at. The chart's own rewrite-target default must be nullified or every API path
breaks. The cert-manager annotation orders a certificate from the
supaspin-letsencrypt issuer; without it the TLS secret keeps whatever supaspin put
there (a saved or a self-signed certificate)."""
anns = {}
if cfg.get("lets_encrypt", True):
anns["cert-manager.io/cluster-issuer"] = ISSUER
if cfg.get("ingress_controller", "nginx") == "traefik":
anns["traefik.ingress.kubernetes.io/router.middlewares"] = \
f"{namespace(n)}-{buffering_middleware(n)}@kubernetescrd"
else:
# The chart default rewrites every path to "/", which breaks all APIs; null removes it.
anns["nginx.ingress.kubernetes.io/rewrite-target"] = None
anns["nginx.ingress.kubernetes.io/proxy-body-size"] = "50m"
anns["nginx.ingress.kubernetes.io/proxy-read-timeout"] = "3600"
anns["nginx.ingress.kubernetes.io/proxy-send-timeout"] = "3600"
if cfg.get("ingress_controller", "nginx") == "traefik":
anns["traefik.ingress.kubernetes.io/router.middlewares"] = \
f"{namespace(n)}-{buffering_middleware(n)}@kubernetescrd"
return anns
+6 -4
View File
@@ -216,13 +216,15 @@ class IngressShaping(unittest.TestCase):
self.assertEqual(a["nginx.ingress.kubernetes.io/proxy-read-timeout"], "3600")
self.assertNotIn("traefik.ingress.kubernetes.io/router.middlewares", a)
def test_traefik_native(self):
def test_traefik_also_keeps_nginx_annotations(self):
# The nginx keys must be present in traefik mode too: while nginx routes the
# ingress they enforce the cap, and they are inert the day Traefik takes over.
a = self.ann({"ingress_controller": "traefik"})
self.assertEqual(a["traefik.ingress.kubernetes.io/router.middlewares"],
"supaspin-2-supa2-buffering@kubernetescrd")
for nginx_key in ("rewrite-target", "proxy-body-size", "proxy-read-timeout",
"proxy-send-timeout"):
self.assertNotIn(f"nginx.ingress.kubernetes.io/{nginx_key}", a)
self.assertIsNone(a["nginx.ingress.kubernetes.io/rewrite-target"])
self.assertEqual(a["nginx.ingress.kubernetes.io/proxy-body-size"], "50m")
self.assertEqual(a["nginx.ingress.kubernetes.io/proxy-read-timeout"], "3600")
self.assertEqual(a["cert-manager.io/cluster-issuer"], "supaspin-letsencrypt")
def test_lets_encrypt_off_drops_cert_manager_annotation(self):