mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
450 lines
16 KiB
Python
450 lines
16 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from argparse import Namespace
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
_RENDER_PATH = REPO_ROOT / "supabase" / "helm" / "render_supabase.py"
|
|
_NOTES_TEMPLATE_PATH = REPO_ROOT / "supabase" / "helm" / "knoe-supabase" / "templates" / "NOTES.txt"
|
|
|
|
|
|
def _load_render_module():
|
|
spec = importlib.util.spec_from_file_location("prole_render_supabase", _RENDER_PATH)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
_render_mod = _load_render_module()
|
|
_build_overlay = _render_mod._build_overlay
|
|
_read_cfg = _render_mod._read_cfg
|
|
_split_frontdoor_docs = _render_mod._split_frontdoor_docs
|
|
|
|
|
|
def _write_cfg(path: Path, content: str) -> None:
|
|
path.write_text(content.strip() + "\n", encoding="utf-8")
|
|
|
|
|
|
def test_render_supabase_uses_supabase_hostname_for_public_urls(tmp_path: Path, monkeypatch):
|
|
# Ensure env doesn't interfere with config-driven behavior.
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[User]
|
|
supabase_hostname = db.prole.org
|
|
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
overlay, _meta = _build_overlay(cfg, args)
|
|
|
|
assert overlay["ingress"]["hosts"][0]["host"] == "db.prole.org"
|
|
assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "https://db.prole.org"
|
|
assert overlay["environment"]["auth"]["GOTRUE_SITE_URL"] == "https://db.prole.org"
|
|
assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "https://db.prole.org"
|
|
|
|
|
|
def test_render_supabase_allows_scheme_in_supabase_hostname(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[User]
|
|
supabase_hostname = http://db.prole.org
|
|
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
overlay, _meta = _build_overlay(cfg, args)
|
|
|
|
assert overlay["ingress"]["hosts"][0]["host"] == "db.prole.org"
|
|
assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "http://db.prole.org"
|
|
assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "http://db.prole.org"
|
|
|
|
|
|
def test_render_supabase_splits_api_and_studio_hosts_for_k8s(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
DEPLOYMENT_MODE = k8s
|
|
SUPABASE_API_HOSTNAME = api.0.knoe.dev
|
|
SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
overlay, _meta = _build_overlay(cfg, args)
|
|
|
|
assert overlay["ingress"]["hosts"][0]["host"] == "api.0.knoe.dev"
|
|
assert overlay["studioIngress"]["hosts"][0]["host"] == "db.0.knoe.dev"
|
|
assert overlay["ingress"]["className"] == "gce"
|
|
assert overlay["studioIngress"]["className"] == "gce"
|
|
assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "https://api.0.knoe.dev"
|
|
assert overlay["environment"]["auth"]["GOTRUE_SITE_URL"] == "https://db.0.knoe.dev"
|
|
assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "https://db.0.knoe.dev"
|
|
assert overlay["environment"]["auth"]["GOTRUE_URI_ALLOW_LIST"] == (
|
|
"https://db.0.knoe.dev/**,https://api.0.knoe.dev/**"
|
|
)
|
|
|
|
|
|
def test_render_supabase_derives_env_indexed_api_host_for_k8s(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("SUPABASE_API_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
DEPLOYMENT_MODE = k8s
|
|
AUTH_HOSTNAME = api.knoe.dev
|
|
SUPABASE_STUDIO_HOSTNAME = db.33.knoe.dev
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
overlay, _meta = _build_overlay(cfg, args)
|
|
|
|
assert overlay["ingress"]["hosts"][0]["host"] == "api.33.knoe.dev"
|
|
assert overlay["studioIngress"]["hosts"][0]["host"] == "db.33.knoe.dev"
|
|
assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "https://api.33.knoe.dev"
|
|
assert overlay["environment"]["auth"]["GOTRUE_SITE_URL"] == "https://db.33.knoe.dev"
|
|
assert overlay["environment"]["auth"]["GOTRUE_URI_ALLOW_LIST"] == (
|
|
"https://db.33.knoe.dev/**,https://api.33.knoe.dev/**"
|
|
)
|
|
|
|
|
|
def test_supabase_notes_template_uses_studio_ingress_host():
|
|
notes_template = _NOTES_TEMPLATE_PATH.read_text(encoding="utf-8")
|
|
|
|
assert (
|
|
"Visit the Studio dashboard at https://{{ (index .Values.studioIngress.hosts 0).host }}"
|
|
in notes_template
|
|
)
|
|
assert "(index .Values.ingress.hosts 0).host" not in notes_template
|
|
|
|
|
|
def test_render_supabase_rejects_duplicate_api_and_studio_host_claims(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
DEPLOYMENT_MODE = k8s
|
|
SUPABASE_API_HOSTNAME = db.0.knoe.dev
|
|
SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
|
|
try:
|
|
_build_overlay(cfg, args)
|
|
except SystemExit as exc:
|
|
assert "Duplicate ingress host/path claim detected" in str(exc)
|
|
else:
|
|
raise AssertionError("Expected duplicate host/path guardrail to abort")
|
|
|
|
|
|
def test_render_supabase_allows_db_cluster_context_for_public_ingress(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
monkeypatch.setenv("KUBECONTEXT", "knoe-dev-cnpg-0")
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
init_cluster.app_cluster_kubecontext = knoe-dev-0
|
|
init_cluster.db_cluster_kubecontext = knoe-dev-cnpg-0
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
DEPLOYMENT_MODE = k8s
|
|
SUPABASE_API_HOSTNAME = api.0.knoe.dev
|
|
SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
|
|
overlay, _meta = _build_overlay(cfg, args)
|
|
|
|
assert overlay["ingress"]["hosts"][0]["host"] == "api.0.knoe.dev"
|
|
assert overlay["studioIngress"]["hosts"][0]["host"] == "db.0.knoe.dev"
|
|
|
|
|
|
def test_render_supabase_rejects_traefik_class_in_k8s_without_opt_in(tmp_path: Path, monkeypatch):
|
|
monkeypatch.delenv("SUPABASE_HOST", raising=False)
|
|
monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False)
|
|
monkeypatch.delenv("KNOE_DB_SERVICE", raising=False)
|
|
|
|
cfg_path = tmp_path / "prole.cfg"
|
|
_write_cfg(
|
|
cfg_path,
|
|
"""
|
|
[Inputs]
|
|
init_password.db_password = test-password
|
|
init_password.db_host_port = 5432
|
|
|
|
[Global]
|
|
NAMESPACE = test-ns
|
|
DEPLOYMENT_MODE = k8s
|
|
SUPABASE_API_HOSTNAME = api.0.knoe.dev
|
|
SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev
|
|
SUPABASE_INGRESS_CLASS = traefik
|
|
STORAGE_BACKEND = local
|
|
""",
|
|
)
|
|
|
|
cfg = _read_cfg(cfg_path)
|
|
args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s"))
|
|
|
|
try:
|
|
_build_overlay(cfg, args)
|
|
except SystemExit as exc:
|
|
assert "incompatible with k8s mode" in str(exc)
|
|
else:
|
|
raise AssertionError("Expected ingress-class guardrail to abort")
|
|
|
|
|
|
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"},"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}],"selector":{"app.kubernetes.io/name":"knoe-supabase-kong","app.kubernetes.io/instance":"supabase"}}}',
|
|
'{"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"}]}}',
|
|
)
|
|
)
|
|
|
|
app_docs, frontdoor_docs = _split_frontdoor_docs(rendered_manifest)
|
|
|
|
app_names = {doc["metadata"]["name"] for doc in app_docs}
|
|
frontdoor_names = {doc["metadata"]["name"] for doc in frontdoor_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-studio" not in app_names
|
|
assert "supabase-kong" in frontdoor_names
|
|
assert "supabase-studio" in frontdoor_names
|
|
assert ("supabase-kong", "Deployment") not in frontdoor_name_kind
|
|
assert ("supabase-studio", "Deployment") not in frontdoor_name_kind
|
|
|
|
kong_service = next(doc for doc in frontdoor_docs if doc["metadata"]["name"] == "supabase-kong" and doc["kind"] == "Service")
|
|
assert kong_service["spec"]["selector"]["app.kubernetes.io/instance"] == "supabase-frontdoor-db"
|
|
for doc in frontdoor_docs:
|
|
assert doc["metadata"]["labels"]["app.kubernetes.io/instance"] == "supabase-frontdoor-db"
|
|
|
|
|
|
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
|
|
app.kubernetes.io/instance: supabase
|
|
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) == 2
|
|
assert {doc["kind"] for doc in frontdoor_docs} == {"Service", "Ingress"}
|
|
for doc in frontdoor_docs:
|
|
assert doc["metadata"]["name"] == "supabase-kong"
|
|
assert doc["metadata"]["namespace"] == "supabase"
|
|
assert doc["metadata"]["labels"]["app.kubernetes.io/instance"] == "supabase-frontdoor-db"
|
|
assert "spec" in doc
|
|
kong_service = next(doc for doc in frontdoor_docs if doc["kind"] == "Service")
|
|
assert kong_service["spec"]["selector"]["app.kubernetes.io/instance"] == "supabase-frontdoor-db"
|
|
|
|
|
|
def test_render_supabase_split_frontdoor_allows_custom_release_label():
|
|
rendered_manifest = """
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: supabase-kong
|
|
spec:
|
|
selector:
|
|
app.kubernetes.io/name: kong
|
|
app.kubernetes.io/instance: supabase
|
|
ports:
|
|
- name: proxy
|
|
port: 8000
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: supabase-kong
|
|
spec:
|
|
rules:
|
|
- host: api.0.knoe.dev
|
|
"""
|
|
|
|
_, frontdoor_docs = _split_frontdoor_docs(rendered_manifest, frontdoor_release="supabase-db-custom")
|
|
|
|
kong_service = next(doc for doc in frontdoor_docs if doc["kind"] == "Service")
|
|
assert kong_service["spec"]["selector"]["app.kubernetes.io/instance"] == "supabase-db-custom"
|
|
for doc in frontdoor_docs:
|
|
assert doc["metadata"]["labels"]["app.kubernetes.io/instance"] == "supabase-db-custom"
|
|
|
|
|
|
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
|