mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
feat: enhance configurable domain and ingress handling for GitLab and Supabase
- Introduced support for dynamic GitLab and Supabase domain and ingress configuration based on deployment mode (k8s vs local). - Default GitLab domain and ingress class aligned with `prole.cfg` deployment settings. - Expanded Supabase support: - Added configurable API and Studio hostnames with default fallbacks. - Introduced separate ingress class configuration for API and Studio. - Standardized public URL normalization for both services. - Updated tests to validate domain, ingress, and configuration behaviors across environments.
This commit is contained in:
parent
091012a9e5
commit
6318d232f0
@ -48,13 +48,13 @@ env_setup.PROLE_DATA = $HOME/dev/prole/data
|
||||
env_setup.PROLE_HOME = $HOME/dev/prole
|
||||
env_setup.PROLE_LOGS = $HOME/dev/prole/logs
|
||||
env_setup.PROLE_SERVICE = $HOME/dev/prole/etc
|
||||
gitops.git_provider = Gitea
|
||||
gitops.git_provider = GitLab
|
||||
gitops.node_selector =
|
||||
init_cluster.argocd_enabled = false
|
||||
init_cluster.at_rest_encryption_enabled = true
|
||||
init_cluster.cluster_env = prod
|
||||
init_cluster.deployment_target = prole-prod-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.gitops_enabled = true
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:vxIxsLYZSClD_vzj:aSQOAaWbKOIT26PQPCqqIwenTzN4YKcaCa8OihlkDA9cdhbHrPeHtaeT3Pu140a67xKPV45ExSEiJ7jnWSW3lOH0KSO56qgzKxMXzrynKFfQVyJp5OzzQeCl-jrz15AdknLhPeh3eTmOEfLuhNY2Yy9fsBQ-l0gYikw37Q==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
@ -103,12 +103,18 @@ DB_HOST_PORT = 5432
|
||||
DB_PASSWORD= vzx.wC4Akd4x-Dj6Wguh
|
||||
DEPLOYMENT_MODE = k8s
|
||||
DEPLOYMENT_TARGET = prole-prod-cluster
|
||||
GITLAB_DOMAIN = git.knoe.dev
|
||||
GITLAB_INGRESS_CLASS = gce
|
||||
DOCKER_PRELOAD = false
|
||||
KNOE_DB_USER = root
|
||||
KUBECONTEXT = gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:wEwOvfE5WeOdbVjW:yjYu4AJqgYo_EnwvYx4fgeSA1coQb8uv8FxE4_NNSIkQadVroCjaEpOyHXcStehLxGl7Bk-elU43yKzYQT_85BkLXp4-9k-LVh_kAfJp2cXlw7q8PczBr3-_MJXeWv1Cw7mCiMuWEfVpKjpCzZ5-URsBsIH3iYz20_6qwg==}
|
||||
SUPABASE_API_HOSTNAME = api.knoe.dev
|
||||
SUPABASE_HOSTNAME = db.0.knoe.dev
|
||||
SUPABASE_INGRESS_CLASS = gce
|
||||
SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
@ -155,7 +161,7 @@ OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
[Optional Features]
|
||||
AT_REST_ENCRYPTION_ENABLED = True
|
||||
GITOPS_ENABLED = False
|
||||
GITOPS_ENABLED = True
|
||||
KERBEROS_ENABLED = True
|
||||
SUPABASE_ENABLED = True
|
||||
|
||||
@ -209,7 +215,7 @@ Configure routing, TLS, and frontdoor domain mappings
|
||||
Prepare migration artifacts from source environment
|
||||
Generate OpenTofu plan and apply production pipeline
|
||||
MODE = k8s
|
||||
OPENTOFU_VARS = {"admin_user":"knoe_admin","app_database":"knoey","app_user":"knoey_app","backup_bucket":"knoe-0-backups","backup_provider":"gcs","backup_retention_days":14,"bootstrap_admin_email":"admin@knoey.com","cluster_name":"knoe-prod","cnpg_cluster_name":"knoe-db","db_instances":3,"db_pgdata_size_gi":100,"db_pgdata_storage_class":"premium-rwo","db_storage_class":"premium-rwo","db_storage_size_gi":100,"db_wal_size_gi":25,"db_wal_storage_class":"premium-rwo","ecosystem_id":0,"environment":"production","frontdoor_host":"knoey.com","gcp_project_id":"plenary-truck-485623-p7","gcp_region":"us-west3","google_hosted_domain":"","meta_database":"knoe_meta","migration_continuous_until_cutover":false,"migration_mode":"snapshot-restore","migration_source_database":"knoey","migration_source_environment":"prole.org","migration_source_host":"knoe-local-db.prole.org","migration_source_password_ref":"secretref://local-source-db-password","migration_source_port":5432,"migration_source_user":"replication_user","namespace":"ecosystem-0","oidc_client_id_ref":"secretref://google-oidc-client-id","oidc_client_secret_ref":"secretref://google-oidc-client-secret","oidc_issuer":"https://accounts.google.com","platform_domain":"knoe.dev","postgres_version":"16","project_id":"plenary-truck-485623-p7","region":"us-west3","tls_mode":"managed","wal_bucket":"knoe-0-wal","workload_identity_sa":"cnpg-backup"}
|
||||
OPENTOFU_VARS = {"admin_user":"knoe_admin","app_database":"knoey","app_user":"knoey_app","backup_bucket":"knoe-0-backups","backup_provider":"gcs","backup_retention_days":14,"bootstrap_admin_email":"admin@knoey.com","cluster_name":"knoe-prod","cnpg_cluster_name":"knoe-db","db_instances":3,"db_pgdata_size_gi":100,"db_pgdata_storage_class":"premium-rwo","db_storage_class":"premium-rwo","db_storage_size_gi":100,"db_wal_size_gi":25,"db_wal_storage_class":"premium-rwo","ecosystem_id":0,"environment":"production","frontdoor_host":"knoey.com","gcp_project_id":"plenary-truck-485623-p7","gcp_region":"us-west3","gitlab_host":"git.knoe.dev","google_hosted_domain":"","meta_database":"knoe_meta","migration_continuous_until_cutover":false,"migration_mode":"snapshot-restore","migration_source_database":"knoey","migration_source_environment":"prole.org","migration_source_host":"knoe-local-db.prole.org","migration_source_password_ref":"secretref://local-source-db-password","migration_source_port":5432,"migration_source_user":"replication_user","namespace":"ecosystem-0","oidc_client_id_ref":"secretref://google-oidc-client-id","oidc_client_secret_ref":"secretref://google-oidc-client-secret","oidc_issuer":"https://accounts.google.com","platform_domain":"knoe.dev","postgres_version":"16","project_id":"plenary-truck-485623-p7","region":"us-west3","supabase_api_host":"api.knoe.dev","supabase_studio_host":"db.0.knoe.dev","tls_mode":"managed","wal_bucket":"knoe-0-wal","workload_identity_sa":"cnpg-backup"}
|
||||
PRODUCTION_CONFIG_YAML = kind: KnoeProductionConfig
|
||||
metadata:
|
||||
ecosystemId: 0
|
||||
@ -260,6 +266,9 @@ spec:
|
||||
routing:
|
||||
frontdoorHost: knoey.com
|
||||
platformDomain: knoe.dev
|
||||
apiHost: api.knoe.dev
|
||||
studioHost: db.0.knoe.dev
|
||||
gitHost: git.knoe.dev
|
||||
tlsMode: managed
|
||||
migration:
|
||||
sourceEnvironment: prole.org
|
||||
@ -278,8 +287,8 @@ spec:
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Deployment]
|
||||
MODE = k3d
|
||||
TARGET = prole-dev-cluster
|
||||
MODE = k8s
|
||||
TARGET = prole-prod-cluster
|
||||
|
||||
[Install]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
@ -31,12 +31,12 @@ Options:
|
||||
--help Show this help
|
||||
|
||||
Behavior:
|
||||
- Removes any pre-existing git.prole.org Gitea configurations (helm release,
|
||||
- Removes any pre-existing GitLab-domain Gitea configurations (helm release,
|
||||
namespace, Kong routes) when --force is specified or when gitea is detected.
|
||||
- Installs the GitLab Operator via Helm into the gitlab namespace.
|
||||
- Provisions a GitLab CR that uses the knoe-db CloudNativePG cluster as its
|
||||
external PostgreSQL data store.
|
||||
- Configures git.prole.org as a Kong-managed ingress pointing to GitLab.
|
||||
- Configures the GitLab public hostname as Kubernetes ingress pointing to GitLab.
|
||||
- Pins all GitLab workloads to the selected node (default: gandalf.prole.org).
|
||||
EOF
|
||||
}
|
||||
@ -116,8 +116,19 @@ REDIS_NAMESPACE="${REDIS_NAMESPACE:-${SERVICE_NAMESPACE:-knoe-system}}"
|
||||
REDIS_HOST="${GITLAB_REDIS_HOST:-redis-master.${REDIS_NAMESPACE}.svc.cluster.local}"
|
||||
REDIS_PORT="${GITLAB_REDIS_PORT:-6379}"
|
||||
|
||||
# GitLab domain (reuses the git.prole.org shared hostname)
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-git.prole.org}"
|
||||
# GitLab public domain (configurable; default follows deployment mode)
|
||||
_default_gitlab_domain="git.prole.org"
|
||||
if [[ "$MODE" == "k8s" ]]; then
|
||||
_default_gitlab_domain="git.knoe.dev"
|
||||
fi
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-${GITLAB_HOSTNAME:-$_default_gitlab_domain}}"
|
||||
|
||||
# Public ingress class (k8s/GKE defaults to gce; local clusters keep kong)
|
||||
_default_gitlab_ingress_class="kong"
|
||||
if [[ "$MODE" == "k8s" ]]; then
|
||||
_default_gitlab_ingress_class="gce"
|
||||
fi
|
||||
GITLAB_INGRESS_CLASS="${GITLAB_INGRESS_CLASS:-$_default_gitlab_ingress_class}"
|
||||
|
||||
# Google Workspace OIDC — FRONTDOOR_HOST gates OmniAuth configuration.
|
||||
# On k3s: api.prole.org is the prole-auth SSO gateway (knoe-auth service).
|
||||
@ -952,13 +963,13 @@ if [[ "${GITLAB_NO_WAIT:-0}" != "1" ]]; then
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kong ingress for git.prole.org -> gitlab-webservice
|
||||
# The GitLab Operator creates the Ingress; this block patches it to Kong class
|
||||
# or creates a fallback Kong-native ingress if the CR-managed one is absent.
|
||||
# Public ingress for configured GitLab domain -> gitlab-webservice
|
||||
# The GitLab Operator creates the Ingress; this block ensures a stable public
|
||||
# class/host mapping if the CR-managed ingress is absent or not usable.
|
||||
# ---------------------------------------------------------------------------
|
||||
WEBSERVICE_SVC="${GITLAB_RELEASE}-webservice-default"
|
||||
|
||||
log "Ensuring Kong ingress for ${GITLAB_DOMAIN} -> ${WEBSERVICE_SVC}:8181 ..."
|
||||
log "Ensuring ingress (${GITLAB_INGRESS_CLASS}) for ${GITLAB_DOMAIN} -> ${WEBSERVICE_SVC}:8181 ..."
|
||||
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: networking.k8s.io/v1
|
||||
@ -967,10 +978,10 @@ metadata:
|
||||
name: gitlab-kong-ingress
|
||||
namespace: ${NAMESPACE}
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: kong
|
||||
kubernetes.io/ingress.class: ${GITLAB_INGRESS_CLASS}
|
||||
konghq.com/strip-path: "false"
|
||||
spec:
|
||||
ingressClassName: kong
|
||||
ingressClassName: ${GITLAB_INGRESS_CLASS}
|
||||
rules:
|
||||
- host: ${GITLAB_DOMAIN}
|
||||
http:
|
||||
|
||||
@ -526,6 +526,50 @@ if [[ -n "$_prole_cfg_file" ]]; then
|
||||
export supabase_hostname="$_cfg_sbh"
|
||||
export SUPABASE_HOSTNAME="$_cfg_sbh"
|
||||
fi
|
||||
|
||||
_cfg_sapi=$(_prole_cfg_extract_key_in_files "SUPABASE_API_HOSTNAME" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_sapi" ]]; then
|
||||
_cfg_sapi=$(_prole_cfg_extract_key_in_files "supabase_api_hostname" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -n "$_cfg_sapi" ]]; then
|
||||
export SUPABASE_API_HOSTNAME="$_cfg_sapi"
|
||||
fi
|
||||
|
||||
_cfg_sstudio=$(_prole_cfg_extract_key_in_files "SUPABASE_STUDIO_HOSTNAME" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_sstudio" ]]; then
|
||||
_cfg_sstudio=$(_prole_cfg_extract_key_in_files "supabase_studio_hostname" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -n "$_cfg_sstudio" ]]; then
|
||||
export SUPABASE_STUDIO_HOSTNAME="$_cfg_sstudio"
|
||||
fi
|
||||
|
||||
_cfg_sing=$(_prole_cfg_extract_key_in_files "SUPABASE_INGRESS_CLASS" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_sing" ]]; then
|
||||
_cfg_sing=$(_prole_cfg_extract_key_in_files "supabase_ingress_class" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -n "$_cfg_sing" ]]; then
|
||||
export SUPABASE_INGRESS_CLASS="$_cfg_sing"
|
||||
fi
|
||||
|
||||
_cfg_gitdom=$(_prole_cfg_extract_key_in_files "GITLAB_DOMAIN" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_gitdom" ]]; then
|
||||
_cfg_gitdom=$(_prole_cfg_extract_key_in_files "gitlab_domain" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -z "$_cfg_gitdom" ]]; then
|
||||
_cfg_gitdom=$(_prole_cfg_extract_key_in_files "GITLAB_HOSTNAME" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -n "$_cfg_gitdom" ]]; then
|
||||
export GITLAB_DOMAIN="$_cfg_gitdom"
|
||||
fi
|
||||
|
||||
_cfg_ging=$(_prole_cfg_extract_key_in_files "GITLAB_INGRESS_CLASS" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_ging" ]]; then
|
||||
_cfg_ging=$(_prole_cfg_extract_key_in_files "gitlab_ingress_class" "${_prole_cfg_files[@]}")
|
||||
fi
|
||||
if [[ -n "$_cfg_ging" ]]; then
|
||||
export GITLAB_INGRESS_CLASS="$_cfg_ging"
|
||||
fi
|
||||
|
||||
_cfg_ctx=$(_prole_cfg_extract_key_in_files "KUBE_CONTEXT_NAME" "${_prole_cfg_files[@]}")
|
||||
if [[ -z "$_cfg_ctx" ]]; then
|
||||
_cfg_ctx=$(_prole_cfg_extract_key_in_files "KUBECTL_CONTEXT" "${_prole_cfg_files[@]}")
|
||||
@ -542,7 +586,7 @@ if [[ -n "$_prole_cfg_file" ]]; then
|
||||
export KUBECONTEXT="$_cfg_ctx"
|
||||
fi
|
||||
fi
|
||||
unset _cfg_ns _cfg_sns _cfg_dm _cfg_sh _cfg_sbh _cfg_ctx _prole_cfg_files _prole_cfg_f
|
||||
unset _cfg_ns _cfg_sns _cfg_dm _cfg_sh _cfg_sbh _cfg_sapi _cfg_sstudio _cfg_sing _cfg_gitdom _cfg_ging _cfg_ctx _prole_cfg_files _prole_cfg_f
|
||||
fi
|
||||
|
||||
if [[ -z "${PROLE_HOME:-}" && -d "$_prole_cfg_home_guess" ]]; then
|
||||
|
||||
@ -73,6 +73,9 @@ class ProdAuthConfig:
|
||||
class ProdRoutingConfig:
|
||||
frontdoorHost: str = "knoey.com"
|
||||
platformDomain: str = "knoe.dev"
|
||||
apiHost: str = "api.knoe.dev"
|
||||
studioHost: str = "db.0.knoe.dev"
|
||||
gitHost: str = "git.knoe.dev"
|
||||
tlsMode: str = "managed"
|
||||
|
||||
|
||||
@ -258,6 +261,9 @@ def build_config(payload: dict[str, Any] | None = None) -> KnoeProductionConfig:
|
||||
|
||||
cfg.routing.frontdoorHost = _str_or_empty(routing.get("frontdoorHost")) or cfg.routing.frontdoorHost
|
||||
cfg.routing.platformDomain = _str_or_empty(routing.get("platformDomain")) or cfg.routing.platformDomain
|
||||
cfg.routing.apiHost = _str_or_empty(routing.get("apiHost")) or cfg.routing.apiHost
|
||||
cfg.routing.studioHost = _str_or_empty(routing.get("studioHost")) or cfg.routing.studioHost
|
||||
cfg.routing.gitHost = _str_or_empty(routing.get("gitHost")) or cfg.routing.gitHost
|
||||
cfg.routing.tlsMode = _str_or_empty(routing.get("tlsMode")) or cfg.routing.tlsMode
|
||||
|
||||
cfg.migration.sourceEnvironment = (
|
||||
@ -313,6 +319,9 @@ def validate_prod_config(config: KnoeProductionConfig) -> tuple[list[str], list[
|
||||
"spec.auth.bootstrapAdminEmail": config.auth.bootstrapAdminEmail,
|
||||
"spec.routing.frontdoorHost": config.routing.frontdoorHost,
|
||||
"spec.routing.platformDomain": config.routing.platformDomain,
|
||||
"spec.routing.apiHost": config.routing.apiHost,
|
||||
"spec.routing.studioHost": config.routing.studioHost,
|
||||
"spec.routing.gitHost": config.routing.gitHost,
|
||||
"spec.migration.sourceEnvironment": config.migration.sourceEnvironment,
|
||||
"spec.migration.sourceHost": config.migration.sourceHost,
|
||||
"spec.migration.sourceDatabase": config.migration.sourceDatabase,
|
||||
@ -348,6 +357,9 @@ def validate_prod_config(config: KnoeProductionConfig) -> tuple[list[str], list[
|
||||
for field, value in (
|
||||
("spec.routing.frontdoorHost", config.routing.frontdoorHost),
|
||||
("spec.routing.platformDomain", config.routing.platformDomain),
|
||||
("spec.routing.apiHost", config.routing.apiHost),
|
||||
("spec.routing.studioHost", config.routing.studioHost),
|
||||
("spec.routing.gitHost", config.routing.gitHost),
|
||||
("spec.migration.sourceEnvironment", config.migration.sourceEnvironment),
|
||||
):
|
||||
if _str_or_empty(value) and not _is_domain(_str_or_empty(value)):
|
||||
@ -410,6 +422,9 @@ def opentofu_vars(config: KnoeProductionConfig) -> dict[str, Any]:
|
||||
"google_hosted_domain": config.auth.googleHostedDomain,
|
||||
"frontdoor_host": config.routing.frontdoorHost,
|
||||
"platform_domain": config.routing.platformDomain,
|
||||
"supabase_api_host": config.routing.apiHost,
|
||||
"supabase_studio_host": config.routing.studioHost,
|
||||
"gitlab_host": config.routing.gitHost,
|
||||
"tls_mode": config.routing.tlsMode,
|
||||
"migration_source_environment": config.migration.sourceEnvironment,
|
||||
"migration_mode": config.migration.mode,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"""
|
||||
Render a personalized Supabase Helm values file and pre-template manifests
|
||||
using the local prole.cfg. Defaults are tailored for knoe's CNPG cluster
|
||||
(knoe-db-rw Service) and Traefik ingress.
|
||||
(knoe-db-rw Service) and unattended k8s ingress.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -106,6 +106,31 @@ def _parse_hosts(raw_hosts: str, fallback: str = "db.0.knoe.dev") -> list[str]:
|
||||
return [fallback]
|
||||
|
||||
|
||||
def _normalize_public_hosts(raw_hosts: str, fallback_host: str) -> tuple[list[str], str]:
|
||||
raw_entries: list[str] = []
|
||||
for entry in (raw_hosts or "").split(","):
|
||||
token = (entry or "").strip()
|
||||
if token and not _is_placeholder(token):
|
||||
raw_entries.append(token)
|
||||
if not raw_entries:
|
||||
raw_entries = [fallback_host]
|
||||
|
||||
host_entries = _parse_hosts(",".join(raw_entries), fallback=fallback_host)
|
||||
primary_host = host_entries[0]
|
||||
|
||||
primary_raw = (raw_entries[0] or "").strip()
|
||||
if "://" in primary_raw:
|
||||
parsed = urllib.parse.urlparse(primary_raw)
|
||||
primary_host = (parsed.hostname or "").strip() or primary_host
|
||||
scheme = (parsed.scheme or "https").strip()
|
||||
netloc = (parsed.netloc or primary_host).strip()
|
||||
public_url = f"{scheme}://{netloc}"
|
||||
else:
|
||||
public_url = f"https://{primary_host}"
|
||||
|
||||
return host_entries, public_url
|
||||
|
||||
|
||||
def _discover_k8s_service_namespace(service_name: str) -> str:
|
||||
try:
|
||||
raw = subprocess.check_output(["kubectl", "get", "svc", "-A", "-o", "json"], text=True)
|
||||
@ -208,9 +233,7 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
anon_key = _jwt(anon_payload, jwt_secret)
|
||||
service_key = _jwt(service_payload, jwt_secret)
|
||||
|
||||
supabase_hostname_raw = _first(
|
||||
os.environ.get("SUPABASE_STUDIO_URL", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_url"),
|
||||
legacy_supabase_hostname_raw = _first(
|
||||
_cfg_get(cfg, "User", "supabase_hostname"),
|
||||
_cfg_get(cfg, "Global", "supabase_hostname"),
|
||||
_cfg_get(cfg, "User", "SUPABASE_HOSTNAME"),
|
||||
@ -220,10 +243,38 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
default="db.0.knoe.dev",
|
||||
).strip()
|
||||
|
||||
if _is_placeholder(supabase_hostname_raw):
|
||||
supabase_hostname_raw = ""
|
||||
if not supabase_hostname_raw:
|
||||
supabase_hostname_raw = "db.0.knoe.dev"
|
||||
if _is_placeholder(legacy_supabase_hostname_raw):
|
||||
legacy_supabase_hostname_raw = ""
|
||||
if not legacy_supabase_hostname_raw:
|
||||
legacy_supabase_hostname_raw = "db.0.knoe.dev"
|
||||
|
||||
studio_hostname_raw = _first(
|
||||
os.environ.get("SUPABASE_STUDIO_URL", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_url"),
|
||||
os.environ.get("SUPABASE_STUDIO_HOSTNAME", ""),
|
||||
_cfg_get(cfg, "User", "SUPABASE_STUDIO_HOSTNAME"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_STUDIO_HOSTNAME"),
|
||||
legacy_supabase_hostname_raw,
|
||||
default="db.0.knoe.dev",
|
||||
).strip()
|
||||
if _is_placeholder(studio_hostname_raw):
|
||||
studio_hostname_raw = ""
|
||||
if not studio_hostname_raw:
|
||||
studio_hostname_raw = legacy_supabase_hostname_raw
|
||||
|
||||
api_hostname_raw = _first(
|
||||
os.environ.get("SUPABASE_API_URL", ""),
|
||||
_cfg_get(cfg, "Inputs", "init_cluster.supabase_api_url"),
|
||||
os.environ.get("SUPABASE_API_HOSTNAME", ""),
|
||||
_cfg_get(cfg, "User", "SUPABASE_API_HOSTNAME"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_API_HOSTNAME"),
|
||||
studio_hostname_raw,
|
||||
default="api.knoe.dev",
|
||||
).strip()
|
||||
if _is_placeholder(api_hostname_raw):
|
||||
api_hostname_raw = ""
|
||||
if not api_hostname_raw:
|
||||
api_hostname_raw = studio_hostname_raw
|
||||
|
||||
studio_enabled = _as_bool(
|
||||
_first(
|
||||
@ -271,30 +322,41 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
default=True,
|
||||
)
|
||||
|
||||
raw_host_entries: list[str] = []
|
||||
for entry in supabase_hostname_raw.split(","):
|
||||
token = (entry or "").strip()
|
||||
if token and not _is_placeholder(token):
|
||||
raw_host_entries.append(token)
|
||||
if not raw_host_entries:
|
||||
raw_host_entries = ["db.0.knoe.dev"]
|
||||
studio_host_entries, studio_public_url = _normalize_public_hosts(
|
||||
studio_hostname_raw,
|
||||
fallback_host="db.0.knoe.dev",
|
||||
)
|
||||
api_host_entries, api_public_url = _normalize_public_hosts(
|
||||
api_hostname_raw,
|
||||
fallback_host=studio_host_entries[0],
|
||||
)
|
||||
|
||||
host_entries = _parse_hosts(",".join(raw_host_entries), fallback="db.0.knoe.dev")
|
||||
ingress_host = host_entries[0]
|
||||
|
||||
# Canonical public URL for Supabase (used for auth callbacks + Studio links).
|
||||
public_url = ""
|
||||
primary_raw_host = (raw_host_entries[0] or "").strip()
|
||||
if "://" in primary_raw_host:
|
||||
parsed = urllib.parse.urlparse(primary_raw_host)
|
||||
ingress_host = (parsed.hostname or "").strip() or ingress_host
|
||||
scheme = (parsed.scheme or "https").strip()
|
||||
if parsed.netloc:
|
||||
public_url = f"{scheme}://{parsed.netloc}"
|
||||
else:
|
||||
public_url = f"{scheme}://{ingress_host}"
|
||||
else:
|
||||
public_url = f"https://{ingress_host}"
|
||||
mode = _first(
|
||||
os.environ.get("PROLE_MODE", ""),
|
||||
_cfg_get(cfg, "Global", "DEPLOYMENT_MODE"),
|
||||
_cfg_get(cfg, "globals", "prole.mode"),
|
||||
default="",
|
||||
).strip().lower()
|
||||
default_ingress_class = "gce" if mode == "k8s" else "traefik"
|
||||
api_ingress_class = _first(
|
||||
os.environ.get("SUPABASE_API_INGRESS_CLASS", ""),
|
||||
os.environ.get("SUPABASE_INGRESS_CLASS", ""),
|
||||
_cfg_get(cfg, "User", "SUPABASE_API_INGRESS_CLASS"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_API_INGRESS_CLASS"),
|
||||
_cfg_get(cfg, "User", "SUPABASE_INGRESS_CLASS"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_INGRESS_CLASS"),
|
||||
default=default_ingress_class,
|
||||
).strip()
|
||||
if not api_ingress_class or _is_placeholder(api_ingress_class):
|
||||
api_ingress_class = default_ingress_class
|
||||
studio_ingress_class = _first(
|
||||
os.environ.get("SUPABASE_STUDIO_INGRESS_CLASS", ""),
|
||||
_cfg_get(cfg, "User", "SUPABASE_STUDIO_INGRESS_CLASS"),
|
||||
_cfg_get(cfg, "Global", "SUPABASE_STUDIO_INGRESS_CLASS"),
|
||||
api_ingress_class,
|
||||
).strip()
|
||||
if not studio_ingress_class or _is_placeholder(studio_ingress_class):
|
||||
studio_ingress_class = api_ingress_class
|
||||
|
||||
# Additional origins/redirect URLs allowed by GoTrue (comma-separated hostnames or URLs).
|
||||
# Needed when a service at a different hostname (e.g. svc.knoe.dev) uses Supabase auth.
|
||||
@ -304,7 +366,13 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
_cfg_get(cfg, "Supabase", "ADDITIONAL_REDIRECT_URLS"),
|
||||
default="",
|
||||
).strip()
|
||||
_extra_redirect_urls: list[str] = []
|
||||
_allow_list_entries: list[str] = []
|
||||
for _base_url in (studio_public_url, api_public_url):
|
||||
if not _base_url:
|
||||
continue
|
||||
_candidate = _base_url.rstrip("/") + "/**"
|
||||
if _candidate not in _allow_list_entries:
|
||||
_allow_list_entries.append(_candidate)
|
||||
for _entry in _extra_redirect_raw.split(","):
|
||||
_entry = _entry.strip()
|
||||
if not _entry or _is_placeholder(_entry):
|
||||
@ -313,17 +381,11 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
_entry = f"https://{_entry}"
|
||||
if not _entry.endswith("/**"):
|
||||
_entry = _entry.rstrip("/") + "/**"
|
||||
_extra_redirect_urls.append(_entry)
|
||||
# Always include the primary public URL itself; only set if extra origins exist
|
||||
_gotrue_uri_allow_list = ",".join([public_url + "/**"] + _extra_redirect_urls) if _extra_redirect_urls else ""
|
||||
if _entry not in _allow_list_entries:
|
||||
_allow_list_entries.append(_entry)
|
||||
_gotrue_uri_allow_list = ",".join(_allow_list_entries)
|
||||
|
||||
# Placement: pin Supabase pods to the configured primary node (default: pi.knoe.dev)
|
||||
mode = _first(
|
||||
os.environ.get("PROLE_MODE", ""),
|
||||
_cfg_get(cfg, "Global", "DEPLOYMENT_MODE"),
|
||||
_cfg_get(cfg, "globals", "prole.mode"),
|
||||
default="",
|
||||
).strip().lower()
|
||||
supabase_primary_node = _first(
|
||||
os.environ.get("SUPABASE_PRIMARY_NODE", ""),
|
||||
os.environ.get("SUPABASE_NODE_SELECTOR", ""),
|
||||
@ -427,13 +489,13 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
"DB_HOST": db_host,
|
||||
"DB_PORT": str(db_port),
|
||||
"DB_SSL": "disable",
|
||||
"API_EXTERNAL_URL": public_url,
|
||||
"GOTRUE_SITE_URL": public_url,
|
||||
"API_EXTERNAL_URL": api_public_url,
|
||||
"GOTRUE_SITE_URL": studio_public_url,
|
||||
**( {"GOTRUE_URI_ALLOW_LIST": _gotrue_uri_allow_list} if _gotrue_uri_allow_list else {} ),
|
||||
},
|
||||
"analytics": {"DB_HOST": db_host, "DB_PORT": str(db_port)},
|
||||
"meta": {"DB_HOST": db_host, "DB_PORT": str(db_port)},
|
||||
"studio": {"SUPABASE_PUBLIC_URL": public_url},
|
||||
"studio": {"SUPABASE_PUBLIC_URL": studio_public_url},
|
||||
},
|
||||
"secret": {
|
||||
"db": {"password": db_password, "database": db_name},
|
||||
@ -442,13 +504,13 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
},
|
||||
"ingress": {
|
||||
"enabled": True,
|
||||
"className": "traefik",
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
|
||||
"className": api_ingress_class,
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in api_host_entries],
|
||||
},
|
||||
"studioIngress": {
|
||||
"enabled": True,
|
||||
"className": "traefik",
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
|
||||
"className": studio_ingress_class,
|
||||
"hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in studio_host_entries],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,9 @@ def test_default_prod_config_uses_knoey_knoe_conventions():
|
||||
assert cfg.backups.walBucket == "knoe-0-wal"
|
||||
assert cfg.routing.frontdoorHost == "knoey.com"
|
||||
assert cfg.routing.platformDomain == "knoe.dev"
|
||||
assert cfg.routing.apiHost == "api.knoe.dev"
|
||||
assert cfg.routing.studioHost == "db.0.knoe.dev"
|
||||
assert cfg.routing.gitHost == "git.knoe.dev"
|
||||
assert cfg.migration.sourceEnvironment == "prole.org"
|
||||
|
||||
|
||||
@ -44,6 +47,7 @@ def test_validation_requires_project_and_enforces_ecosystem_and_positive_numbers
|
||||
cfg.backups.retentionDays = 0
|
||||
cfg.migration.sourcePort = 0
|
||||
cfg.routing.frontdoorHost = "not_a_domain"
|
||||
cfg.routing.apiHost = "not_a_domain"
|
||||
|
||||
errors, _warnings = validate_prod_config(cfg)
|
||||
|
||||
@ -54,6 +58,7 @@ def test_validation_requires_project_and_enforces_ecosystem_and_positive_numbers
|
||||
assert "spec.backups.retentionDays must be a positive number" in errors
|
||||
assert "spec.migration.sourcePort must be a positive number" in errors
|
||||
assert "spec.routing.frontdoorHost must be a valid domain" in errors
|
||||
assert "spec.routing.apiHost must be a valid domain" in errors
|
||||
|
||||
|
||||
def test_yaml_and_opentofu_preview_contains_expected_values():
|
||||
@ -73,6 +78,9 @@ def test_yaml_and_opentofu_preview_contains_expected_values():
|
||||
assert "pgdataStorageClass: standard-rwo" in yaml_preview
|
||||
assert "walStorageClass: premium-rwo" in yaml_preview
|
||||
assert "frontdoorHost: knoey.com" in yaml_preview
|
||||
assert "apiHost: api.knoe.dev" in yaml_preview
|
||||
assert "studioHost: db.0.knoe.dev" in yaml_preview
|
||||
assert "gitHost: git.knoe.dev" in yaml_preview
|
||||
assert tf_vars["project_id"] == "my-gcp-project"
|
||||
assert tf_vars["namespace"] == "ecosystem-0"
|
||||
assert tf_vars["cnpg_cluster_name"] == "knoe-db"
|
||||
@ -80,6 +88,9 @@ def test_yaml_and_opentofu_preview_contains_expected_values():
|
||||
assert tf_vars["db_wal_storage_class"] == "premium-rwo"
|
||||
assert tf_vars["db_pgdata_size_gi"] == 120
|
||||
assert tf_vars["db_wal_size_gi"] == 32
|
||||
assert tf_vars["supabase_api_host"] == "api.knoe.dev"
|
||||
assert tf_vars["supabase_studio_host"] == "db.0.knoe.dev"
|
||||
assert tf_vars["gitlab_host"] == "git.knoe.dev"
|
||||
|
||||
|
||||
def test_prod_config_api_plan_apply_status_and_logs_flow():
|
||||
|
||||
@ -45,6 +45,7 @@ def test_render_supabase_uses_supabase_hostname_for_public_urls(tmp_path: Path,
|
||||
|
||||
[Global]
|
||||
NAMESPACE = test-ns
|
||||
STORAGE_BACKEND = local
|
||||
""",
|
||||
)
|
||||
|
||||
@ -77,6 +78,7 @@ def test_render_supabase_allows_scheme_in_supabase_hostname(tmp_path: Path, monk
|
||||
|
||||
[Global]
|
||||
NAMESPACE = test-ns
|
||||
STORAGE_BACKEND = local
|
||||
""",
|
||||
)
|
||||
|
||||
@ -87,3 +89,42 @@ def test_render_supabase_allows_scheme_in_supabase_hostname(tmp_path: Path, monk
|
||||
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.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.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.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.knoe.dev/**"
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user