11import os
2+ import socket
23import logging
34
4- from localstack_paradedb .utils .docker import DatabaseDockerContainerExtension
5+ from localstack_extensions .utils .docker import ProxiedDockerContainerExtension
6+ from localstack .extensions .api import http
7+ from localstack .utils .docker_utils import DOCKER_CLIENT
8+ from localstack .utils .container_utils .container_client import PortMappings
9+ from localstack .utils .sync import retry
10+ from werkzeug .datastructures import Headers
511
612LOG = logging .getLogger (__name__ )
713
1824DEFAULT_POSTGRES_PORT = 5432
1925
2026
21- class ParadeDbExtension (DatabaseDockerContainerExtension ):
27+ class ParadeDbExtension (ProxiedDockerContainerExtension ):
2228 name = "paradedb"
2329
2430 # Name of the Docker image to spin up
@@ -34,37 +40,97 @@ def __init__(self):
3440 postgres_port = int (os .environ .get (ENV_POSTGRES_PORT , DEFAULT_POSTGRES_PORT ))
3541
3642 # Environment variables to pass to the container
37- env_vars = {
43+ self . env_vars = {
3844 "POSTGRES_USER" : postgres_user ,
3945 "POSTGRES_PASSWORD" : postgres_password ,
4046 "POSTGRES_DB" : postgres_db ,
4147 }
4248
43- super ().__init__ (
44- image_name = self .DOCKER_IMAGE ,
45- container_ports = [postgres_port ],
46- env_vars = env_vars ,
47- )
48-
4949 # Store configuration for connection info
5050 self .postgres_user = postgres_user
5151 self .postgres_password = postgres_password
5252 self .postgres_db = postgres_db
5353 self .postgres_port = postgres_port
5454
55+ super ().__init__ (
56+ image_name = self .DOCKER_IMAGE ,
57+ container_ports = [postgres_port ],
58+ )
59+
60+ def should_proxy_request (self , headers : Headers ) -> bool :
61+ """
62+ Define whether a request should be proxied based on request headers.
63+ For database extensions, this is not used as connections are direct TCP.
64+ """
65+ return False
66+
67+ def update_gateway_routes (self , router : http .Router [http .RouteHandler ]):
68+ """
69+ Override to start container without setting up HTTP gateway routes.
70+ Database extensions don't need HTTP routing - clients connect directly via TCP.
71+ """
72+ self .start_container ()
73+
74+ def start_container (self ) -> None :
75+ """Override to add env_vars support and database-specific health checking."""
76+ LOG .debug ("Starting extension container %s" , self .container_name )
77+
78+ port_mapping = PortMappings ()
79+ for port in self .container_ports :
80+ port_mapping .add (port )
81+
82+ try :
83+ DOCKER_CLIENT .run_container (
84+ self .image_name ,
85+ detach = True ,
86+ remove = True ,
87+ name = self .container_name ,
88+ ports = port_mapping ,
89+ env_vars = self .env_vars ,
90+ )
91+ except Exception as e :
92+ LOG .debug ("Failed to start container %s: %s" , self .container_name , e )
93+ raise
94+
95+ def _check_health ():
96+ """Check if PostgreSQL port is accepting connections."""
97+ self ._check_tcp_port (self .container_host , self .postgres_port )
98+
99+ try :
100+ retry (_check_health , retries = 60 , sleep = 1 )
101+ except Exception as e :
102+ LOG .info ("Failed to connect to container %s: %s" , self .container_name , e )
103+ self ._remove_container ()
104+ raise
105+
106+ LOG .info (
107+ "Successfully started extension container %s on %s:%s" ,
108+ self .container_name ,
109+ self .container_host ,
110+ self .postgres_port ,
111+ )
112+
113+ def _check_tcp_port (self , host : str , port : int , timeout : float = 2.0 ) -> None :
114+ """Check if a TCP port is accepting connections."""
115+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
116+ sock .settimeout (timeout )
117+ try :
118+ sock .connect ((host , port ))
119+ sock .close ()
120+ except (socket .timeout , socket .error ) as e :
121+ raise AssertionError (f"Port { port } not ready: { e } " )
122+
55123 def get_connection_info (self ) -> dict :
56124 """Return connection information for ParadeDB."""
57- info = super ().get_connection_info ()
58- info .update (
59- {
60- "database" : self .postgres_db ,
61- "user" : self .postgres_user ,
62- "password" : self .postgres_password ,
63- "port" : self .postgres_port ,
64- "connection_string" : (
65- f"postgresql://{ self .postgres_user } :{ self .postgres_password } "
66- f"@{ self .container_host } :{ self .postgres_port } /{ self .postgres_db } "
67- ),
68- }
69- )
70- return info
125+ return {
126+ "host" : self .container_host ,
127+ "database" : self .postgres_db ,
128+ "user" : self .postgres_user ,
129+ "password" : self .postgres_password ,
130+ "port" : self .postgres_port ,
131+ "ports" : {self .postgres_port : self .postgres_port },
132+ "connection_string" : (
133+ f"postgresql://{ self .postgres_user } :{ self .postgres_password } "
134+ f"@{ self .container_host } :{ self .postgres_port } /{ self .postgres_db } "
135+ ),
136+ }
0 commit comments