11import os
2- import logging
2+ import socket
33
4- from localstack_paradedb .utils .docker import DatabaseDockerContainerExtension
5-
6- LOG = logging .getLogger (__name__ )
4+ from localstack_extensions .utils .docker import ProxiedDockerContainerExtension
5+ from localstack import config
76
87# Environment variables for configuration
98ENV_POSTGRES_USER = "PARADEDB_POSTGRES_USER"
109ENV_POSTGRES_PASSWORD = "PARADEDB_POSTGRES_PASSWORD"
1110ENV_POSTGRES_DB = "PARADEDB_POSTGRES_DB"
12- ENV_POSTGRES_PORT = "PARADEDB_POSTGRES_PORT"
1311
1412# Default values
1513DEFAULT_POSTGRES_USER = "myuser"
1816DEFAULT_POSTGRES_PORT = 5432
1917
2018
21- class ParadeDbExtension (DatabaseDockerContainerExtension ):
19+ class ParadeDbExtension (ProxiedDockerContainerExtension ):
2220 name = "paradedb"
2321
2422 # Name of the Docker image to spin up
@@ -31,7 +29,13 @@ def __init__(self):
3129 ENV_POSTGRES_PASSWORD , DEFAULT_POSTGRES_PASSWORD
3230 )
3331 postgres_db = os .environ .get (ENV_POSTGRES_DB , DEFAULT_POSTGRES_DB )
34- postgres_port = int (os .environ .get (ENV_POSTGRES_PORT , DEFAULT_POSTGRES_PORT ))
32+ postgres_port = DEFAULT_POSTGRES_PORT
33+
34+ # Store configuration for connection info
35+ self .postgres_user = postgres_user
36+ self .postgres_password = postgres_password
37+ self .postgres_db = postgres_db
38+ self .postgres_port = postgres_port
3539
3640 # Environment variables to pass to the container
3741 env_vars = {
@@ -40,31 +44,70 @@ def __init__(self):
4044 "POSTGRES_DB" : postgres_db ,
4145 }
4246
47+ def _tcp_health_check ():
48+ """Check if ParadeDB port is accepting connections."""
49+ self ._check_tcp_port (self .container_host , self .postgres_port )
50+
4351 super ().__init__ (
4452 image_name = self .DOCKER_IMAGE ,
4553 container_ports = [postgres_port ],
4654 env_vars = env_vars ,
55+ health_check_fn = _tcp_health_check ,
56+ tcp_ports = [postgres_port ], # Enable TCP proxying through gateway
4757 )
4858
49- # Store configuration for connection info
50- self .postgres_user = postgres_user
51- self .postgres_password = postgres_password
52- self .postgres_db = postgres_db
53- self .postgres_port = postgres_port
59+ def tcp_connection_matcher (self , data : bytes ) -> bool :
60+ """
61+ Identify PostgreSQL/ParadeDB connections by protocol handshake.
62+
63+ PostgreSQL can start with either:
64+ 1. SSL request: protocol code 80877103 (0x04D2162F)
65+ 2. Startup message: protocol version 3.0 (0x00030000)
66+
67+ Both use the same format:
68+ - 4 bytes: message length
69+ - 4 bytes: protocol version/code
70+ """
71+ if len (data ) < 8 :
72+ return False
73+
74+ # Check for SSL request (80877103 = 0x04D2162F)
75+ if data [4 :8 ] == b"\x04 \xd2 \x16 \x2f " :
76+ return True
77+
78+ # Check for protocol version 3.0 (0x00030000)
79+ if data [4 :8 ] == b"\x00 \x03 \x00 \x00 " :
80+ return True
81+
82+ return False
83+
84+ def _check_tcp_port (self , host : str , port : int , timeout : float = 2.0 ) -> None :
85+ """Check if a TCP port is accepting connections."""
86+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
87+ sock .settimeout (timeout )
88+ try :
89+ sock .connect ((host , port ))
90+ sock .close ()
91+ except (socket .timeout , socket .error ) as e :
92+ raise AssertionError (f"Port { port } not ready: { e } " )
5493
5594 def get_connection_info (self ) -> dict :
5695 """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
96+ # Clients should connect through the LocalStack gateway
97+ gateway_host = "paradedb.localhost.localstack.cloud"
98+ gateway_port = config .LOCALSTACK_HOST .port
99+
100+ return {
101+ "host" : gateway_host ,
102+ "database" : self .postgres_db ,
103+ "user" : self .postgres_user ,
104+ "password" : self .postgres_password ,
105+ "port" : gateway_port ,
106+ "connection_string" : (
107+ f"postgresql://{ self .postgres_user } :{ self .postgres_password } "
108+ f"@{ gateway_host } :{ gateway_port } /{ self .postgres_db } "
109+ ),
110+ # Also include container connection details for debugging
111+ "container_host" : self .container_host ,
112+ "container_port" : self .postgres_port ,
113+ }
0 commit comments