chore: refactor Supabase manifest generation and split frontdoor logic

- Updated `_extract_k8s_docs` to use `yaml.safe_load_all` for improved parsing and reliability.
- Enhanced `_split_frontdoor_docs` with stricter validation of Deployment, Service, and Ingress specs.
- Added namespace enforcement for frontdoor resources during manifest splitting.
- Refactored and optimized test cases to cover new validation and splitting functionality.
This commit is contained in:
chrisfu 2026-04-13 11:45:11 -07:00
parent 7fadc8d00c
commit c07b50d7c3
3 changed files with 164 additions and 52 deletions

View File

@ -1,6 +1,8 @@
{
"values": "/Users/chrisfu/dev/prole/supabase/helm/generated/values.generated.json",
"manifests": "/Users/chrisfu/dev/prole/supabase/k8s/supabase-helm.yaml",
"db_host": "10.180.15.236",
"manifests_app": "/Users/chrisfu/dev/prole/supabase/helm/generated/supabase-helm.app.yaml",
"manifests_frontdoor_db": "/Users/chrisfu/dev/prole/supabase/helm/generated/supabase-helm.frontdoor-db.yaml",
"db_host": "knoe-db-rw.knoe-db-0.svc.cluster.local",
"supabase_namespace": "supabase"
}

View File

@ -21,6 +21,8 @@ import urllib.parse
from pathlib import Path
from typing import Any, Dict
import yaml
# Allow imports from the repo root
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
@ -133,46 +135,8 @@ def _normalize_public_hosts(raw_hosts: str, fallback_host: str) -> tuple[list[st
def _extract_k8s_docs(rendered_manifest: str) -> list[dict[str, Any]]:
docs: list[dict[str, Any]] = []
for chunk in rendered_manifest.split("\n---"):
text = chunk.strip()
if not text:
continue
try:
parsed = json.loads(text)
except Exception:
lines = text.splitlines()
api_version = ""
kind = ""
name = ""
namespace = ""
for idx, raw_line in enumerate(lines):
stripped = raw_line.strip()
if stripped.startswith("apiVersion:"):
api_version = stripped.split(":", 1)[1].strip()
elif stripped.startswith("kind:"):
kind = stripped.split(":", 1)[1].strip()
elif stripped == "metadata:":
j = idx + 1
while j < len(lines):
meta_line = lines[j]
if meta_line and not meta_line.startswith(" "):
break
meta_stripped = meta_line.strip()
if meta_stripped.startswith("name:"):
name = meta_stripped.split(":", 1)[1].strip()
elif meta_stripped.startswith("namespace:"):
namespace = meta_stripped.split(":", 1)[1].strip()
j += 1
if kind:
docs.append(
{
"apiVersion": api_version,
"kind": kind,
"metadata": {"name": name, "namespace": namespace},
}
)
continue
if isinstance(parsed, dict) and parsed.get("kind"):
for parsed in yaml.safe_load_all(rendered_manifest):
if isinstance(parsed, dict) and parsed.get("kind") and parsed.get("apiVersion"):
docs.append(parsed)
return docs
@ -197,7 +161,46 @@ def _dump_manifest_docs(path: Path, docs: list[dict[str, Any]]) -> None:
path.write_text(f"{payload}\n")
def _split_frontdoor_docs(rendered_manifest: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
def _is_valid_frontdoor_doc(doc: dict[str, Any]) -> bool:
kind = str(doc.get("kind") or "")
spec = doc.get("spec")
if kind not in {"Deployment", "Service", "Ingress"}:
return True
if not isinstance(spec, dict):
return False
if kind == "Service":
ports = spec.get("ports")
return isinstance(ports, list) and len(ports) > 0
if kind == "Deployment":
selector = spec.get("selector")
template = spec.get("template")
if not isinstance(selector, dict) or not isinstance(template, dict):
return False
match_labels = selector.get("matchLabels")
template_meta = template.get("metadata")
template_spec = template.get("spec")
if not isinstance(match_labels, dict) or not match_labels:
return False
if not isinstance(template_meta, dict):
return False
labels = template_meta.get("labels")
if not isinstance(labels, dict) or not labels:
return False
if not isinstance(template_spec, dict):
return False
containers = template_spec.get("containers")
return isinstance(containers, list) and len(containers) > 0
rules = spec.get("rules")
default_backend = spec.get("defaultBackend")
has_rules = isinstance(rules, list) and len(rules) > 0
has_default_backend = isinstance(default_backend, dict) and len(default_backend) > 0
return has_rules or has_default_backend
def _split_frontdoor_docs(rendered_manifest: str, namespace: str = "") -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
docs = _extract_k8s_docs(rendered_manifest)
studio_kinds = {"Deployment", "Service", "Ingress"}
kong_kinds = {"Deployment", "Service", "Ingress"}
@ -209,9 +212,9 @@ def _split_frontdoor_docs(rendered_manifest: str) -> tuple[list[dict[str, Any]],
}
frontdoor_docs: list[dict[str, Any]] = []
frontdoor_keys: set[tuple[str, str, str, str]] = set()
frontdoor_indexes: set[int] = set()
for doc in docs:
for idx, doc in enumerate(docs):
kind = str(doc.get("kind") or "")
meta = doc.get("metadata") or {}
name = ""
@ -221,10 +224,18 @@ def _split_frontdoor_docs(rendered_manifest: str) -> tuple[list[dict[str, Any]],
is_studio = name in studio_names and kind in studio_kinds
is_kong = name in kong_names and kind in kong_kinds
if is_studio or is_kong:
frontdoor_indexes.add(idx)
if not _is_valid_frontdoor_doc(doc):
continue
if namespace:
if not isinstance(meta, dict):
meta = {}
doc["metadata"] = meta
if not str(meta.get("namespace") or "").strip():
meta["namespace"] = namespace
frontdoor_docs.append(doc)
frontdoor_keys.add(_doc_key(doc))
app_docs = [doc for doc in docs if _doc_key(doc) not in frontdoor_keys]
app_docs = [doc for idx, doc in enumerate(docs) if idx not in frontdoor_indexes]
return app_docs, frontdoor_docs
@ -907,7 +918,7 @@ def render(args: argparse.Namespace) -> None:
raise SystemExit(f"Helm template failed (code {proc.returncode})")
rendered_path.write_text(proc.stdout)
app_docs, frontdoor_docs = _split_frontdoor_docs(proc.stdout)
app_docs, frontdoor_docs = _split_frontdoor_docs(proc.stdout, namespace=meta["supabase_namespace"])
app_rendered_path = gen_dir / "supabase-helm.app.yaml"
frontdoor_rendered_path = gen_dir / "supabase-helm.frontdoor-db.yaml"
_dump_manifest_docs(app_rendered_path, app_docs)

View File

@ -276,11 +276,11 @@ def test_render_supabase_splits_frontdoor_manifests_for_db_cluster_rollout():
rendered_manifest = "\n---\n".join(
(
'{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"supabase-auth","namespace":"supabase"}}',
'{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"supabase-kong","namespace":"supabase"}}',
'{"apiVersion":"v1","kind":"Service","metadata":{"name":"supabase-kong","namespace":"supabase"}}',
'{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"name":"supabase-kong","namespace":"supabase"}}',
'{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"supabase-studio","namespace":"supabase"}}',
'{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"name":"supabase-studio","namespace":"supabase"}}',
'{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"supabase-kong","namespace":"supabase"},"spec":{"selector":{"matchLabels":{"app":"supabase-kong"}},"template":{"metadata":{"labels":{"app":"supabase-kong"}},"spec":{"containers":[{"name":"kong","image":"kong:latest"}]}}}}',
'{"apiVersion":"v1","kind":"Service","metadata":{"name":"supabase-kong","namespace":"supabase"},"spec":{"ports":[{"name":"proxy","port":8000}]}}',
'{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"name":"supabase-kong","namespace":"supabase"},"spec":{"rules":[{"host":"api.0.knoe.dev"}]}}',
'{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"supabase-studio","namespace":"supabase"},"spec":{"selector":{"matchLabels":{"app":"supabase-studio"}},"template":{"metadata":{"labels":{"app":"supabase-studio"}},"spec":{"containers":[{"name":"studio","image":"supabase/studio:latest"}]}}}}',
'{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"name":"supabase-studio","namespace":"supabase"},"spec":{"rules":[{"host":"db.0.knoe.dev"}]}}',
)
)
@ -292,3 +292,102 @@ def test_render_supabase_splits_frontdoor_manifests_for_db_cluster_rollout():
assert "supabase-auth" in app_names
assert "supabase-kong" in frontdoor_names
assert "supabase-studio" in frontdoor_names
def test_render_supabase_split_frontdoor_preserves_complete_yaml_docs():
rendered_manifest = """
# Source: knoe-supabase/templates/kong-service.yaml
apiVersion: v1
kind: Service
metadata:
name: supabase-kong
spec:
selector:
app.kubernetes.io/name: kong
ports:
- name: proxy
port: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-kong
spec:
selector:
matchLabels:
app: supabase-kong
template:
metadata:
labels:
app: supabase-kong
spec:
containers:
- name: kong
image: kong:latest
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: supabase-kong
spec:
rules:
- host: api.0.knoe.dev
"""
app_docs, frontdoor_docs = _split_frontdoor_docs(rendered_manifest, namespace="supabase")
assert app_docs == []
assert len(frontdoor_docs) == 3
assert {doc["kind"] for doc in frontdoor_docs} == {"Service", "Deployment", "Ingress"}
for doc in frontdoor_docs:
assert doc["metadata"]["name"] == "supabase-kong"
assert doc["metadata"]["namespace"] == "supabase"
assert "spec" in doc
def test_render_supabase_split_frontdoor_skips_invalid_kong_stubs():
rendered_manifest = """
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-auth
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: supabase-kong
spec: {}
---
apiVersion: v1
kind: Service
metadata:
name: supabase-kong
spec:
ports: []
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: supabase-kong
spec: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: supabase-studio
spec:
rules:
- host: db.0.knoe.dev
"""
app_docs, frontdoor_docs = _split_frontdoor_docs(rendered_manifest)
app_names = {doc["metadata"]["name"] for doc in app_docs}
frontdoor_name_kind = {(doc["metadata"]["name"], doc["kind"]) for doc in frontdoor_docs}
assert "supabase-auth" in app_names
assert "supabase-kong" not in app_names
assert ("supabase-kong", "Deployment") not in frontdoor_name_kind
assert ("supabase-kong", "Service") not in frontdoor_name_kind
assert ("supabase-kong", "Ingress") not in frontdoor_name_kind
assert ("supabase-studio", "Ingress") in frontdoor_name_kind