Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7cc13a2
extract utils into separate localstack-extensions-utils package
whummer Jan 27, 2026
b9bb5c5
restructure utils package to localstack.extensions.utils namespace
whummer Jan 27, 2026
feee96f
change module namespace from localstack.extensions to localstack_exte…
whummer Jan 27, 2026
222e5d5
add Makefile and update README for utils package
whummer Jan 27, 2026
1e33cac
remove unused import in docker.py
whummer Jan 27, 2026
d247fa4
add tests for utils package using grpcbin
whummer Jan 29, 2026
f12fc23
add tmp change to fix dependencies
whummer Jan 29, 2026
6c022c2
add CI workflow for utils package tests
whummer Jan 29, 2026
0ae9566
add localstack to test dependencies for utils
whummer Jan 29, 2026
c1b329a
add jsonpatch explicitly to dev and test dependencies
whummer Jan 29, 2026
12816b6
remove unused proto files and grpcio dependencies
whummer Jan 29, 2026
13272e0
minor fixes
whummer Jan 29, 2026
e665310
restore utils package exports in __init__.py
whummer Jan 29, 2026
7516611
use DOCKER_CLIENT instead of subprocess in test fixtures
whummer Jan 29, 2026
334b73c
clean up integration tests: remove redundant asserts, clarify purpose
whummer Jan 29, 2026
25e69a5
simplify test setup: remove TcpForwarder tests and custom markers
whummer Jan 29, 2026
c1cd55e
improve test reliability with synchronization primitives
whummer Jan 29, 2026
6d57e38
fix linter
whummer Jan 29, 2026
426e040
rewrite ParadeDbExtension to use ProxiedDockerContainerExtension as base
whummer Jan 30, 2026
28295e2
Refactor to use ProxiedDockerContainerExtension with better abstractions
whummer Jan 30, 2026
cb5bf71
Consolidate integration tests and add gRPC end-to-end tests
whummer Jan 30, 2026
c7677bd
ruff fixes
purcell Jan 30, 2026
0f3e62b
Add hook-based TCP proxy with gateway integration
whummer Jan 31, 2026
87fa1ae
add TCP proxy logic for ParadeDB
whummer Jan 31, 2026
f53c70e
reduce peek bytes length
whummer Jan 31, 2026
324084e
fix matcher logic; clean up logging; add Postgres SSL matching support
whummer Jan 31, 2026
b48ccf4
minor renaming for better API consistency
whummer Jan 31, 2026
6db748c
refactor wiremock extension to use utils base class as well
whummer Jan 31, 2026
4bfc623
refactor utils tests to use embedded twisted server; minor polishing
whummer Jan 31, 2026
8117793
minor cleanup
whummer Jan 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/miniflare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ jobs:
docker pull localstack/localstack-pro &
pip install localstack localstack-ext

# TODO remove
mkdir ~/.localstack; echo '{"token":"test"}' > ~/.localstack/auth.json

branchName=${GITHUB_HEAD_REF##*/}
if [ "$branchName" = "" ]; then branchName=main; fi
echo "Installing from branch name $branchName"
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/utils.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: LocalStack Extensions Utils Tests

on:
push:
paths:
- utils/**
branches:
- main
pull_request:
paths:
- .github/workflows/utils.yml
- utils/**
workflow_dispatch:

jobs:
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
cd utils
pip install -e .[dev,test]

- name: Lint
run: |
cd utils
make lint

- name: Run unit tests
run: |
cd utils
make test-unit

integration-tests:
name: Run Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
cd utils
pip install -e .[dev,test]

- name: Run integration tests
run: |
docker pull moul/grpcbin &
cd utils
make test-integration
1 change: 1 addition & 0 deletions .github/workflows/wiremock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
pip install localstack terraform-local awscli-local[ver1]

make install
make lint
make dist
localstack extensions -v install file://$(ls ./dist/localstack_wiremock-*.tar.gz)

Expand Down
2 changes: 1 addition & 1 deletion paradedb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ entrypoints: venv ## Generate plugin entrypoints for Python package
$(VENV_RUN); python -m plux entrypoints

format: ## Run ruff to format the codebase
$(VENV_RUN); python -m ruff format .; make lint
$(VENV_RUN); python -m ruff format .; python -m ruff check --fix .

lint: ## Run ruff to lint the codebase
$(VENV_RUN); python -m ruff check --output-format=full .
Expand Down
92 changes: 68 additions & 24 deletions paradedb/localstack_paradedb/extension.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import os
import logging
import socket

from localstack_paradedb.utils.docker import DatabaseDockerContainerExtension

LOG = logging.getLogger(__name__)
from localstack_extensions.utils.docker import ProxiedDockerContainerExtension
from localstack import config

# Environment variables for configuration
ENV_POSTGRES_USER = "PARADEDB_POSTGRES_USER"
Expand All @@ -18,7 +17,7 @@
DEFAULT_POSTGRES_PORT = 5432


class ParadeDbExtension(DatabaseDockerContainerExtension):
class ParadeDbExtension(ProxiedDockerContainerExtension):
name = "paradedb"

# Name of the Docker image to spin up
Expand All @@ -33,38 +32,83 @@ def __init__(self):
postgres_db = os.environ.get(ENV_POSTGRES_DB, DEFAULT_POSTGRES_DB)
postgres_port = int(os.environ.get(ENV_POSTGRES_PORT, DEFAULT_POSTGRES_PORT))

# Store configuration for connection info
self.postgres_user = postgres_user
self.postgres_password = postgres_password
self.postgres_db = postgres_db
self.postgres_port = postgres_port

# Environment variables to pass to the container
env_vars = {
"POSTGRES_USER": postgres_user,
"POSTGRES_PASSWORD": postgres_password,
"POSTGRES_DB": postgres_db,
}

def _tcp_health_check():
"""Check if ParadeDB port is accepting connections."""
self._check_tcp_port(self.container_host, self.postgres_port)

super().__init__(
image_name=self.DOCKER_IMAGE,
container_ports=[postgres_port],
env_vars=env_vars,
health_check_fn=_tcp_health_check,
tcp_ports=[postgres_port], # Enable TCP proxying through gateway
)

# Store configuration for connection info
self.postgres_user = postgres_user
self.postgres_password = postgres_password
self.postgres_db = postgres_db
self.postgres_port = postgres_port
def tcp_connection_matcher(self, data: bytes) -> bool:
"""
Identify PostgreSQL/ParadeDB connections by protocol handshake.

PostgreSQL can start with either:
1. SSL request: protocol code 80877103 (0x04D2162F)
2. Startup message: protocol version 3.0 (0x00030000)

Both use the same format:
- 4 bytes: message length
- 4 bytes: protocol version/code
"""
if len(data) < 8:
return False

# Check for SSL request (80877103 = 0x04D2162F)
if data[4:8] == b"\x04\xd2\x16\x2f":
return True

# Check for protocol version 3.0 (0x00030000)
if data[4:8] == b"\x00\x03\x00\x00":
return True

return False

def _check_tcp_port(self, host: str, port: int, timeout: float = 2.0) -> None:
"""Check if a TCP port is accepting connections."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
sock.connect((host, port))
sock.close()
except (socket.timeout, socket.error) as e:
raise AssertionError(f"Port {port} not ready: {e}")

def get_connection_info(self) -> dict:
"""Return connection information for ParadeDB."""
info = super().get_connection_info()
info.update(
{
"database": self.postgres_db,
"user": self.postgres_user,
"password": self.postgres_password,
"port": self.postgres_port,
"connection_string": (
f"postgresql://{self.postgres_user}:{self.postgres_password}"
f"@{self.container_host}:{self.postgres_port}/{self.postgres_db}"
),
}
)
return info
# Clients should connect through the LocalStack gateway
gateway_host = "paradedb.localhost.localstack.cloud"
gateway_port = config.LOCALSTACK_HOST.port

return {
"host": gateway_host,
"database": self.postgres_db,
"user": self.postgres_user,
"password": self.postgres_password,
"port": gateway_port,
"connection_string": (
f"postgresql://{self.postgres_user}:{self.postgres_password}"
f"@{gateway_host}:{gateway_port}/{self.postgres_db}"
),
# Also include container connection details for debugging
"container_host": self.container_host,
"container_port": self.postgres_port,
}
144 changes: 0 additions & 144 deletions paradedb/localstack_paradedb/utils/docker.py

This file was deleted.

6 changes: 5 additions & 1 deletion paradedb/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ authors = [
]
keywords = ["LocalStack", "ParadeDB", "PostgreSQL", "Search", "Analytics"]
classifiers = []
dependencies = []
dependencies = [
# TODO remove / replace prior to merge!
# "localstack-extensions-utils",
"localstack-extensions-utils @ git+https://github.com/localstack/localstack-extensions.git@extract-utils-package#subdirectory=utils"
]

[project.urls]
Homepage = "https://github.com/localstack/localstack-extensions"
Expand Down
5 changes: 3 additions & 2 deletions paradedb/tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@


# Connection details for ParadeDB
HOST = "localhost"
PORT = 5432
# Connect through LocalStack gateway with TCP proxying
HOST = "paradedb.localhost.localstack.cloud"
PORT = 4566
USER = "myuser"
PASSWORD = "mypassword"
DATABASE = "mydatabase"
Expand Down
Loading