1- """Add and backfill branch.db_port
1+ """Add branch.db_port
22
33Revision ID: 2b4e8f1a6c03
44Revises: f4f677e4e9b9
55Create Date: 2026-03-19 00:00:00.000000
66
77"""
8- import logging
9- import os
108from typing import Sequence , Union
11- from uuid import UUID
129
1310from alembic import op
1411import sqlalchemy as sa
15- from kubernetes import client as kubernetes_client
16- from kubernetes import config as kubernetes_config
17- from kubernetes .config .config_exception import ConfigException
18- from ulid import ULID
19-
20- logger = logging .getLogger (__name__ )
2112
2213# revision identifiers, used by Alembic.
2314revision : str = '2b4e8f1a6c03'
2415down_revision : Union [str , Sequence [str ], None ] = 'a1b2c3d4e5f6'
2516branch_labels : Union [str , Sequence [str ], None ] = None
2617depends_on : Union [str , Sequence [str ], None ] = None
2718
28- _CHART_NAME = "vela"
29-
30-
31- def _ensure_kube_client_config () -> None :
32- try :
33- kubernetes_config .load_incluster_config ()
34- except ConfigException :
35- try :
36- kubernetes_config .load_kube_config ()
37- except ConfigException as exc :
38- raise RuntimeError ("Kubernetes client not configured. Mount kubeconfig or run in-cluster." ) from exc
39-
40-
41- def _uuid_to_ulid_str (uuid_str : str ) -> str :
42- """Convert a UUID string (as stored in the DB) back to a ULID string."""
43- return str (ULID .from_bytes (UUID (uuid_str ).bytes ))
44-
45-
46- def _deployment_namespace (branch_id : str ) -> str :
47- prefix = os .environ .get ("VELA_DEPLOYMENT_NAMESPACE_PREFIX" , "vela" )
48- ulid_str = _uuid_to_ulid_str (branch_id ).lower ()
49- return f"{ prefix } -{ ulid_str } " if prefix else ulid_str
50-
51-
52- def _release_fullname () -> str :
53- release = os .environ .get ("VELA_DEPLOYMENT_RELEASE_NAME" , "vela" )
54- return release if _CHART_NAME in release else f"{ release } -{ _CHART_NAME } "
55-
56-
57- def _branch_service_name (component : str ) -> str :
58- return f"{ _release_fullname ()} -{ component } "
59-
60-
61- def _get_all_node_ports () -> dict [str , int ]:
62- """Fetch node ports for all pgbouncer/db services in a single API call.
63-
64- Returns a mapping of namespace -> nodePort, preferring the pgbouncer service
65- over the db service when both exist in the same namespace.
66- """
67- core_v1 = kubernetes_client .CoreV1Api ()
68- target_names = {_branch_service_name ("pgbouncer" ), _branch_service_name ("db" )}
69-
70- svc_list = core_v1 .list_service_for_all_namespaces ()
71-
72- ports_by_namespace : dict [str , int ] = {}
73- for svc in svc_list .items :
74- if svc .metadata .name not in target_names :
75- continue
76- ns = svc .metadata .namespace
77- if ns in ports_by_namespace :
78- continue # already resolved for this namespace
79- ports = svc .spec .ports
80- if ports and ports [0 ].node_port :
81- ports_by_namespace [ns ] = ports [0 ].node_port
82-
83- return ports_by_namespace
84-
8519
8620def upgrade () -> None :
8721 """Upgrade schema."""
@@ -90,38 +24,6 @@ def upgrade() -> None:
9024 sa .Column ('db_port' , sa .Integer (), nullable = True ),
9125 )
9226
93- bind = op .get_bind ()
94- branch_rows = bind .execute (
95- sa .text ("SELECT id::text AS id FROM branch" )
96- ).mappings ().all ()
97-
98- if not branch_rows :
99- return
100-
101- _ensure_kube_client_config ()
102- ports_by_namespace = _get_all_node_ports ()
103-
104- updates = []
105- for row in branch_rows :
106- namespace = _deployment_namespace (row ["id" ])
107- port = ports_by_namespace .get (namespace )
108- if port is None :
109- logger .warning ("Could not resolve db_port for branch %s — skipping" , row ["id" ])
110- continue
111- updates .append ({"id" : row ["id" ], "db_port" : port })
112-
113- if updates :
114- bind .execute (
115- sa .text (
116- """
117- UPDATE branch
118- SET db_port = :db_port
119- WHERE id = CAST(:id AS uuid)
120- """
121- ),
122- updates ,
123- )
124-
12527
12628def downgrade () -> None :
12729 """Downgrade schema."""
0 commit comments