Skip to content

Commit a166a21

Browse files
committed
add initial version of cockroachdb extension
1 parent f186d3e commit a166a21

8 files changed

Lines changed: 413 additions & 0 deletions

File tree

cockroachdb/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.venv
2+
dist
3+
build
4+
**/*.egg-info
5+
.eggs

cockroachdb/Makefile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
VENV_BIN = python3 -m venv
2+
VENV_DIR ?= .venv
3+
VENV_ACTIVATE = $(VENV_DIR)/bin/activate
4+
VENV_RUN = . $(VENV_ACTIVATE)
5+
TEST_PATH ?= tests
6+
7+
usage: ## Shows usage for this Makefile
8+
@cat Makefile | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
9+
10+
venv: $(VENV_ACTIVATE)
11+
12+
$(VENV_ACTIVATE): pyproject.toml
13+
test -d .venv || $(VENV_BIN) .venv
14+
$(VENV_RUN); pip install --upgrade pip setuptools plux
15+
$(VENV_RUN); pip install -e .[dev]
16+
touch $(VENV_DIR)/bin/activate
17+
18+
clean:
19+
rm -rf .venv/
20+
rm -rf build/
21+
rm -rf .eggs/
22+
rm -rf *.egg-info/
23+
24+
install: venv ## Install dependencies
25+
$(VENV_RUN); python -m plux entrypoints
26+
27+
dist: venv ## Create distribution
28+
$(VENV_RUN); python -m build
29+
30+
publish: clean-dist venv dist ## Publish extension to pypi
31+
$(VENV_RUN); pip install --upgrade twine; twine upload dist/*
32+
33+
entrypoints: venv ## Generate plugin entrypoints for Python package
34+
$(VENV_RUN); python -m plux entrypoints
35+
36+
format: ## Run ruff to format the codebase
37+
$(VENV_RUN); python -m ruff format .; python -m ruff check --fix .
38+
39+
lint: ## Run ruff to lint the codebase
40+
$(VENV_RUN); python -m ruff check --output-format=full .
41+
42+
test: ## Run integration tests (requires LocalStack running with the Extension installed)
43+
$(VENV_RUN); pytest $(PYTEST_ARGS) $(TEST_PATH)
44+
45+
clean-dist: clean
46+
rm -rf dist/
47+
48+
.PHONY: clean clean-dist dist install publish usage venv format test

cockroachdb/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# CockroachDB on LocalStack
2+
3+
This repo contains a [LocalStack Extension](https://github.com/localstack/localstack-extensions) that facilitates developing [CockroachDB](https://www.cockroachlabs.com)-based applications locally.
4+
5+
CockroachDB is a distributed SQL database built for cloud applications. It is PostgreSQL wire-protocol compatible, making it easy to use existing PostgreSQL drivers and tools.
6+
7+
After installing the extension, a CockroachDB server instance will become available and can be accessed using standard PostgreSQL clients or CockroachDB-specific drivers.
8+
9+
## Connection Details
10+
11+
Once the extension is running, you can connect to CockroachDB using any PostgreSQL-compatible client:
12+
13+
- **Host**: `cockroachdb.localhost.localstack.cloud`
14+
- **Port**: `4566` (LocalStack gateway)
15+
- **Database**: `defaultdb`
16+
- **Username**: `root`
17+
- **Password**: none (insecure mode)
18+
19+
Example connection using `psql`:
20+
```bash
21+
psql "postgresql://root@cockroachdb.localhost.localstack.cloud:4566/defaultdb?sslmode=disable"
22+
```
23+
24+
Example connection using Python with psycopg2:
25+
```python
26+
import psycopg2
27+
28+
conn = psycopg2.connect(
29+
host="cockroachdb.localhost.localstack.cloud",
30+
port=4566,
31+
user="root",
32+
database="defaultdb",
33+
sslmode="disable",
34+
)
35+
cursor = conn.cursor()
36+
cursor.execute("SELECT version()")
37+
print(cursor.fetchone()[0])
38+
conn.close()
39+
```
40+
41+
## Configuration
42+
43+
The following environment variables can be passed to the LocalStack container to configure the extension:
44+
45+
* `COCKROACHDB_IMAGE`: Docker image to use (default: `cockroachdb/cockroach:latest`)
46+
* `COCKROACHDB_FLAGS`: Extra flags appended to the CockroachDB startup command (default: none)
47+
* `COCKROACHDB_USER`: User for connection string reference (default: `root`)
48+
* `COCKROACHDB_DB`: Database for connection string reference (default: `defaultdb`)
49+
50+
Example:
51+
```bash
52+
COCKROACHDB_FLAGS="--cache=.25 --max-sql-memory=.25" localstack start
53+
```
54+
55+
## Known Limitations
56+
57+
* **Single-node only** — this extension runs CockroachDB in `start-single-node` mode. Multi-node clusters are not supported.
58+
* **Insecure mode only** — TLS and authentication are disabled. This is intentional for local development. Do not use in production.
59+
* **Ephemeral data** — data is lost when the CockroachDB container stops, matching LocalStack's stateless default behavior.
60+
61+
## Prerequisites
62+
63+
* Docker
64+
* LocalStack Pro (free trial available)
65+
* `localstack` CLI
66+
* `make`
67+
68+
## Install from GitHub repository
69+
70+
This extension can be installed directly from this Github repo via:
71+
72+
```bash
73+
localstack extensions install "git+https://github.com/localstack/localstack-extensions.git#egg=localstack-extension-cockroachdb&subdirectory=cockroachdb"
74+
```
75+
76+
## Install local development version
77+
78+
Please refer to the docs [here](https://github.com/localstack/localstack-extensions?tab=readme-ov-file#start-localstack-with-the-extension) for instructions on how to start the extension in developer mode.
79+
80+
## Change Log
81+
82+
* `0.1.0`: Initial version of the extension
83+
84+
## License
85+
86+
The code in this repo is available under the Apache 2.0 license.

cockroachdb/localstack_cockroachdb/__init__.py

Whitespace-only changes.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import os
2+
import shlex
3+
import socket
4+
5+
from localstack import config
6+
from localstack.extensions.api import http
7+
from localstack_extensions.utils.docker import ProxiedDockerContainerExtension
8+
9+
# Environment variables for configuration
10+
ENV_IMAGE = "COCKROACHDB_IMAGE"
11+
ENV_FLAGS = "COCKROACHDB_FLAGS"
12+
ENV_USER = "COCKROACHDB_USER"
13+
ENV_DB = "COCKROACHDB_DB"
14+
15+
# Defaults
16+
DEFAULT_IMAGE = "cockroachdb/cockroach:latest"
17+
DEFAULT_USER = "root"
18+
DEFAULT_DB = "defaultdb"
19+
DEFAULT_PORT = 26257
20+
21+
22+
class CockroachDbExtension(ProxiedDockerContainerExtension):
23+
name = "cockroachdb"
24+
25+
# Base command args passed to the cockroachdb/cockroach Docker image entrypoint.
26+
# --store=type=mem,size=1GiB: in-memory store — faster startup, truly ephemeral,
27+
# avoids filesystem permission issues inside the container.
28+
# Note: CockroachDB requires at least 640 MiB for in-memory store.
29+
BASE_COMMAND = ["start-single-node", "--insecure", "--store=type=mem,size=1GiB"]
30+
31+
def __init__(self):
32+
image = os.environ.get(ENV_IMAGE, DEFAULT_IMAGE)
33+
extra_flags = shlex.split((os.environ.get(ENV_FLAGS) or "").strip())
34+
35+
# Store for connection info (not passed to container — insecure mode
36+
# auto-creates the root user and defaultdb database)
37+
self.cockroach_user = os.environ.get(ENV_USER, DEFAULT_USER)
38+
self.cockroach_db = os.environ.get(ENV_DB, DEFAULT_DB)
39+
40+
def _health_check():
41+
self._check_tcp_port(self.container_host, DEFAULT_PORT)
42+
43+
super().__init__(
44+
image_name=image,
45+
container_ports=[DEFAULT_PORT],
46+
command=self.BASE_COMMAND + extra_flags,
47+
health_check_fn=_health_check,
48+
health_check_retries=120, # 2 minutes — CockroachDB can be slow on first start
49+
tcp_ports=[DEFAULT_PORT],
50+
)
51+
52+
def update_gateway_routes(self, router: http.Router[http.RouteHandler]):
53+
"""
54+
Override to set up only TCP routing without HTTP proxy.
55+
56+
CockroachDB uses the native PostgreSQL wire protocol (not HTTP), so we
57+
only need TCP protocol routing — not HTTP proxying. Adding an HTTP
58+
proxy without a host restriction would cause all HTTP requests to be
59+
forwarded to the CockroachDB container, breaking other services.
60+
"""
61+
self.start_container()
62+
63+
if self.tcp_ports:
64+
self._setup_tcp_protocol_routing()
65+
66+
def tcp_connection_matcher(self, data: bytes) -> bool:
67+
"""
68+
Identify CockroachDB/PostgreSQL connections by protocol handshake.
69+
70+
CockroachDB speaks the PostgreSQL wire protocol. Connections start with:
71+
1. SSL request: protocol code 80877103 (0x04D2162F)
72+
2. Startup message: protocol version 3.0 (0x00030000)
73+
"""
74+
if len(data) < 8:
75+
return False
76+
77+
# SSL request (80877103 = 0x04D2162F)
78+
if data[4:8] == b"\x04\xd2\x16\x2f":
79+
return True
80+
81+
# Protocol version 3.0 (0x00030000)
82+
if data[4:8] == b"\x00\x03\x00\x00":
83+
return True
84+
85+
return False
86+
87+
def _check_tcp_port(self, host: str, port: int, timeout: float = 2.0) -> None:
88+
"""Check if a TCP port is accepting connections."""
89+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
90+
sock.settimeout(timeout)
91+
try:
92+
sock.connect((host, port))
93+
sock.close()
94+
except (socket.timeout, socket.error) as e:
95+
raise AssertionError(f"Port {port} not ready: {e}")
96+
97+
def get_connection_info(self) -> dict:
98+
"""Return connection information for CockroachDB."""
99+
gateway_host = "cockroachdb.localhost.localstack.cloud"
100+
gateway_port = config.LOCALSTACK_HOST.port
101+
102+
return {
103+
"host": gateway_host,
104+
"port": gateway_port,
105+
"user": self.cockroach_user,
106+
"database": self.cockroach_db,
107+
"connection_string": (
108+
f"cockroachdb+psycopg2://{self.cockroach_user}"
109+
f"@{gateway_host}:{gateway_port}/{self.cockroach_db}"
110+
f"?sslmode=disable"
111+
),
112+
}

cockroachdb/pyproject.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[build-system]
2+
requires = ["setuptools", "wheel", "plux>=1.3.1"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "localstack-extension-cockroachdb"
7+
version = "0.1.0"
8+
description = "LocalStack Extension: CockroachDB on LocalStack"
9+
readme = {file = "README.md", content-type = "text/markdown; charset=UTF-8"}
10+
requires-python = ">=3.10"
11+
authors = [
12+
{ name = "LocalStack team"}
13+
]
14+
keywords = ["LocalStack", "CockroachDB", "PostgreSQL", "SQL", "Distributed"]
15+
classifiers = []
16+
dependencies = [
17+
"localstack-extensions-utils"
18+
]
19+
20+
[project.urls]
21+
Homepage = "https://github.com/localstack/localstack-extensions"
22+
23+
[project.optional-dependencies]
24+
dev = [
25+
"boto3",
26+
"build",
27+
"jsonpatch",
28+
"psycopg2-binary",
29+
"pytest",
30+
"rolo",
31+
"ruff",
32+
]
33+
34+
[project.entry-points."localstack.extensions"]
35+
localstack_cockroachdb = "localstack_cockroachdb.extension:CockroachDbExtension"

cockroachdb/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)