Skip to content

Commit e96b76d

Browse files
committed
test: crunchybridge integration
1 parent d71cf3f commit e96b76d

File tree

13 files changed

+687
-104
lines changed

13 files changed

+687
-104
lines changed

bin/e2e

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,27 @@ set -euo pipefail
77
# Hardcoded test database connection string (PostgreSQL 16 superuser)
88
# It's a very small database with only a users table with 3 users.
99
PG_16_CONN="postgres://postgres:hCazJJexWqQCzRghehroazRkj5nDbGMpW9JjjtoL5lBFPGi6auHbQ8jZHUZYEluO@p.mgdruzjwa5hk7elhmlnug4somq.db.postgresbridge.com:5432/quic_test"
10+
CRUNCHYBRIDGE_API_KEY="cbkey_16we5jO0whSk843bLuVa7jdni90TaB2lmdVw091SYUkZrn"
11+
CRUNCHYBRIDGE_CLUSTER_NAME="quic-e2e"
12+
CRUNCHYBRIDGE_DATABASE_NAME="quic_test"
1013

1114
# Default PostgreSQL version
1215
PG_VERSION="${TEST_POSTGRES_VERSION:-16}"
1316

1417
# Use hardcoded connection string if not set in environment
1518
export TEST_CONNECTION_STRING="${TEST_CONNECTION_STRING:-$PG_16_CONN}"
1619

20+
# Export Crunchy Bridge credentials for e2e tests
21+
export CRUNCHYBRIDGE_API_KEY="${CRUNCHYBRIDGE_API_KEY}"
22+
export CRUNCHYBRIDGE_CLUSTER_NAME="${CRUNCHYBRIDGE_CLUSTER_NAME}"
23+
export CRUNCHYBRIDGE_DATABASE_NAME="${CRUNCHYBRIDGE_DATABASE_NAME}"
24+
1725
echo "========================================="
1826
echo "Branchd E2E Test Runner"
1927
echo "========================================="
2028
echo "PostgreSQL version: ${PG_VERSION}"
2129
echo "Connection: ${TEST_CONNECTION_STRING%%@*}@***"
30+
echo "Crunchy Bridge cluster: ${CRUNCHYBRIDGE_CLUSTER_NAME}"
2231
echo ""
2332

2433
# Check for Terraform variables
@@ -49,13 +58,6 @@ terraform init -upgrade >/dev/null
4958
terraform apply -auto-approve \
5059
-var "postgres_version=${PG_VERSION}"
5160

52-
# Get VM details
53-
PUBLIC_IP=$(terraform output -raw public_ip)
54-
API_URL=$(terraform output -raw api_url)
55-
echo "VM provisioned: ${PUBLIC_IP}"
56-
echo "API URL: ${API_URL}"
57-
echo "Infrastructure setup complete (PostgreSQL, ZFS, Redis, Caddy)"
58-
5961
cd ../..
6062

6163
echo ""
@@ -66,14 +68,14 @@ export TEST_POSTGRES_VERSION="${PG_VERSION}"
6668

6769
# Run tests with verbose output
6870
# BuildAndDeploy is called by the test itself
69-
go test ./tests/e2e -v -timeout 10m
71+
# Running only Crunchy Bridge test
72+
go test ./tests/e2e -v -timeout 5m -run TestCrunchyBridgeIntegration
7073

7174
echo ""
7275
echo "========================================="
7376
echo "Tests completed successfully!"
7477
echo "========================================="
7578
echo ""
76-
echo "VM is still running at: ${PUBLIC_IP}"
7779
echo "To destroy the VM:"
7880
echo " cd tests/terraform && terraform destroy -auto-approve"
7981
echo ""

internal/branches/create-branch.sh

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,9 @@ echo "Cleaning up PostgreSQL files..."
282282
sudo -u postgres rm -f "${BRANCH_PGDATA}/postmaster.pid"
283283
sudo -u postgres rm -f "${BRANCH_PGDATA}/postgresql.auto.conf"
284284

285-
# Copy config files from system location to data directory
286-
# Ubuntu's pg_createcluster stores configs in /etc/postgresql/, but branches need them in the data dir
287-
echo "Copying PostgreSQL config files to data directory..."
288-
sudo -u postgres cp "/etc/postgresql/${PG_VERSION}/main/postgresql.conf" "${BRANCH_PGDATA}/postgresql.conf"
289-
sudo -u postgres cp "/etc/postgresql/${PG_VERSION}/main/pg_hba.conf" "${BRANCH_PGDATA}/pg_hba.conf.bak"
290-
sudo -u postgres cp "/etc/postgresql/${PG_VERSION}/main/pg_ident.conf" "${BRANCH_PGDATA}/pg_ident.conf"
285+
# Config files already exist in the ZFS clone from the source restore
286+
# No need to copy - they were cloned from the restore's data directory
287+
echo "Using PostgreSQL config files from cloned restore..."
291288

292289
# Minimal update to postgresql.conf for the new port.
293290
# Note: SSL configuration is inherited from the main cluster via ZFS clone

internal/server/anon_rules_handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
type CreateAnonRuleRequest struct {
1515
Table string `json:"table" binding:"required"`
1616
Column string `json:"column" binding:"required"`
17-
Template json.RawMessage `json:"template" binding:"required"`
17+
Template json.RawMessage `json:"template" binding:"required" swaggertype:"string" example:"\"user_${index}@example.com\""`
1818
Type string `json:"type"` // Optional: "text", "integer", "boolean", "null" - overrides auto-detection
1919
}
2020

internal/server/config_handlers.go

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,36 @@ type OnboardingDatabaseRequest struct {
2525

2626
// ConfigResponse represents the configuration response
2727
type ConfigResponse struct {
28-
ID string `json:"id"`
29-
ConnectionString string `json:"connection_string"`
30-
PostgresVersion string `json:"postgres_version"`
31-
SchemaOnly bool `json:"schema_only"`
32-
RefreshSchedule string `json:"refresh_schedule"`
33-
BranchPostgresqlConf string `json:"branch_postgresql_conf"`
34-
DatabaseName string `json:"database_name"`
35-
Domain string `json:"domain"`
36-
LetsEncryptEmail string `json:"lets_encrypt_email"`
37-
MaxRestores int `json:"max_restores"`
38-
LastRefreshedAt *time.Time `json:"last_refreshed_at"`
39-
NextRefreshAt *time.Time `json:"next_refresh_at"`
40-
CreatedAt time.Time `json:"created_at"`
28+
ID string `json:"id"`
29+
ConnectionString string `json:"connection_string"`
30+
PostgresVersion string `json:"postgres_version"`
31+
SchemaOnly bool `json:"schema_only"`
32+
RefreshSchedule string `json:"refresh_schedule"`
33+
BranchPostgresqlConf string `json:"branch_postgresql_conf"`
34+
DatabaseName string `json:"database_name"`
35+
Domain string `json:"domain"`
36+
LetsEncryptEmail string `json:"lets_encrypt_email"`
37+
MaxRestores int `json:"max_restores"`
38+
LastRefreshedAt *time.Time `json:"last_refreshed_at"`
39+
NextRefreshAt *time.Time `json:"next_refresh_at"`
40+
CreatedAt time.Time `json:"created_at"`
41+
CrunchyBridgeAPIKey string `json:"crunchy_bridge_api_key"`
42+
CrunchyBridgeClusterName string `json:"crunchy_bridge_cluster_name"`
43+
CrunchyBridgeDatabaseName string `json:"crunchy_bridge_database_name"`
4144
}
4245

4346
// UpdateConfigRequest represents the request to update configuration
4447
type UpdateConfigRequest struct {
45-
ConnectionString string `json:"connectionString"`
46-
PostgresVersion string `json:"postgresVersion"`
47-
SchemaOnly *bool `json:"schemaOnly"`
48-
RefreshSchedule string `json:"refreshSchedule"`
49-
Domain string `json:"domain"`
50-
LetsEncryptEmail string `json:"letsEncryptEmail"`
51-
MaxRestores *int `json:"maxRestores"`
48+
ConnectionString string `json:"connectionString"`
49+
PostgresVersion string `json:"postgresVersion"`
50+
SchemaOnly *bool `json:"schemaOnly"`
51+
RefreshSchedule string `json:"refreshSchedule"`
52+
Domain string `json:"domain"`
53+
LetsEncryptEmail string `json:"letsEncryptEmail"`
54+
MaxRestores *int `json:"maxRestores"`
55+
CrunchyBridgeAPIKey string `json:"crunchyBridgeApiKey"`
56+
CrunchyBridgeClusterName string `json:"crunchyBridgeClusterName"`
57+
CrunchyBridgeDatabaseName string `json:"crunchyBridgeDatabaseName"`
5258
}
5359

5460
// @Summary Get configuration
@@ -72,19 +78,22 @@ func (s *Server) getConfig(c *gin.Context) {
7278
}
7379

7480
c.JSON(http.StatusOK, ConfigResponse{
75-
ID: config.ID,
76-
ConnectionString: redactConnectionString(config.ConnectionString),
77-
PostgresVersion: config.PostgresVersion,
78-
SchemaOnly: config.SchemaOnly,
79-
RefreshSchedule: config.RefreshSchedule,
80-
BranchPostgresqlConf: config.BranchPostgresqlConf,
81-
DatabaseName: config.DatabaseName,
82-
Domain: config.Domain,
83-
LetsEncryptEmail: config.LetsEncryptEmail,
84-
MaxRestores: config.MaxRestores,
85-
LastRefreshedAt: config.LastRefreshedAt,
86-
NextRefreshAt: config.NextRefreshAt,
87-
CreatedAt: config.CreatedAt,
81+
ID: config.ID,
82+
ConnectionString: redactConnectionString(config.ConnectionString),
83+
PostgresVersion: config.PostgresVersion,
84+
SchemaOnly: config.SchemaOnly,
85+
RefreshSchedule: config.RefreshSchedule,
86+
BranchPostgresqlConf: config.BranchPostgresqlConf,
87+
DatabaseName: config.DatabaseName,
88+
Domain: config.Domain,
89+
LetsEncryptEmail: config.LetsEncryptEmail,
90+
MaxRestores: config.MaxRestores,
91+
LastRefreshedAt: config.LastRefreshedAt,
92+
NextRefreshAt: config.NextRefreshAt,
93+
CreatedAt: config.CreatedAt,
94+
CrunchyBridgeAPIKey: redactSecret(config.CrunchyBridgeAPIKey),
95+
CrunchyBridgeClusterName: config.CrunchyBridgeClusterName,
96+
CrunchyBridgeDatabaseName: config.CrunchyBridgeDatabaseName,
8897
})
8998
}
9099

@@ -117,6 +126,29 @@ func (s *Server) updateConfig(c *gin.Context) {
117126
return
118127
}
119128

129+
// Update Crunchy Bridge configuration if provided
130+
if req.CrunchyBridgeAPIKey != "" {
131+
config.CrunchyBridgeAPIKey = req.CrunchyBridgeAPIKey
132+
}
133+
if req.CrunchyBridgeClusterName != "" {
134+
config.CrunchyBridgeClusterName = req.CrunchyBridgeClusterName
135+
}
136+
if req.CrunchyBridgeDatabaseName != "" {
137+
config.CrunchyBridgeDatabaseName = req.CrunchyBridgeDatabaseName
138+
}
139+
140+
// Clear connection string if Crunchy Bridge fields are being set
141+
if req.CrunchyBridgeAPIKey != "" && req.ConnectionString == "" {
142+
config.ConnectionString = ""
143+
}
144+
145+
// Clear Crunchy Bridge fields if connection string is being set
146+
if req.ConnectionString != "" && req.CrunchyBridgeAPIKey == "" {
147+
config.CrunchyBridgeAPIKey = ""
148+
config.CrunchyBridgeClusterName = ""
149+
config.CrunchyBridgeDatabaseName = ""
150+
}
151+
120152
// Update connection string if provided
121153
if req.ConnectionString != "" {
122154
// Validate new connection string
@@ -238,22 +270,33 @@ func (s *Server) updateConfig(c *gin.Context) {
238270
s.logger.Info().Str("config_id", config.ID).Msg("Configuration updated")
239271

240272
c.JSON(http.StatusOK, ConfigResponse{
241-
ID: config.ID,
242-
ConnectionString: redactConnectionString(config.ConnectionString),
243-
PostgresVersion: config.PostgresVersion,
244-
SchemaOnly: config.SchemaOnly,
245-
RefreshSchedule: config.RefreshSchedule,
246-
BranchPostgresqlConf: config.BranchPostgresqlConf,
247-
DatabaseName: config.DatabaseName,
248-
Domain: config.Domain,
249-
LetsEncryptEmail: config.LetsEncryptEmail,
250-
MaxRestores: config.MaxRestores,
251-
LastRefreshedAt: config.LastRefreshedAt,
252-
NextRefreshAt: config.NextRefreshAt,
253-
CreatedAt: config.CreatedAt,
273+
ID: config.ID,
274+
ConnectionString: redactConnectionString(config.ConnectionString),
275+
PostgresVersion: config.PostgresVersion,
276+
SchemaOnly: config.SchemaOnly,
277+
RefreshSchedule: config.RefreshSchedule,
278+
BranchPostgresqlConf: config.BranchPostgresqlConf,
279+
DatabaseName: config.DatabaseName,
280+
Domain: config.Domain,
281+
LetsEncryptEmail: config.LetsEncryptEmail,
282+
MaxRestores: config.MaxRestores,
283+
LastRefreshedAt: config.LastRefreshedAt,
284+
NextRefreshAt: config.NextRefreshAt,
285+
CreatedAt: config.CreatedAt,
286+
CrunchyBridgeAPIKey: redactSecret(config.CrunchyBridgeAPIKey),
287+
CrunchyBridgeClusterName: config.CrunchyBridgeClusterName,
288+
CrunchyBridgeDatabaseName: config.CrunchyBridgeDatabaseName,
254289
})
255290
}
256291

292+
// redactSecret replaces a secret value with *** if it's not empty
293+
func redactSecret(secret string) string {
294+
if secret == "" {
295+
return ""
296+
}
297+
return "***"
298+
}
299+
257300
// redactConnectionString replaces the password with *** in a PostgreSQL connection string
258301
func redactConnectionString(connStr string) string {
259302
if connStr == "" {

internal/workers/crunchy_bridge_restore.sh

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,84 @@ log "Configuring PostgreSQL..."
125125
# Copy TLS certificates (shared across all clusters)
126126
sudo -u postgres cp /etc/postgresql-common/ssl/server.crt "${DATA_DIR}/"
127127
sudo -u postgres cp /etc/postgresql-common/ssl/server.key "${DATA_DIR}/"
128-
129-
# Update postgresql.conf - append our settings
130-
sudo -u postgres tee -a "${DATA_DIR}/postgresql.conf" > /dev/null << EOF
131-
132-
# Branchd settings
133-
port = ${PG_PORT}
128+
# Fix permissions on server.key (PostgreSQL requires 0600)
129+
sudo -u postgres chmod 0600 "${DATA_DIR}/server.key"
130+
sudo -u postgres chmod 0644 "${DATA_DIR}/server.crt"
131+
132+
# Replace postgresql.conf with a clean config optimized for ephemeral dev branches
133+
# This removes all Crunchy Bridge specific settings and production-oriented configs
134+
log "Creating clean postgresql.conf for dev branch..."
135+
sudo -u postgres tee "${DATA_DIR}/postgresql.conf" > /dev/null << 'EOF'
136+
# Connection Settings
134137
listen_addresses = '127.0.0.1'
138+
port = ${PG_PORT}
139+
max_connections = 1000
135140
136141
# TLS/SSL
137142
ssl = on
138143
ssl_cert_file = 'server.crt'
139144
ssl_key_file = 'server.key'
145+
ssl_min_protocol_version = 'TLSv1.2'
146+
147+
# Authentication
148+
password_encryption = scram-sha-256
149+
150+
# Resource Limits
151+
# Note: These parameters are set high to support pgBackRest recovery from production backups.
152+
# PostgreSQL requires these values to be >= the source database during WAL replay.
153+
shared_buffers = 128MB
154+
work_mem = 8MB
155+
maintenance_work_mem = 64MB
156+
effective_cache_size = 512MB
157+
max_worker_processes = 200
158+
max_parallel_workers_per_gather = 2
159+
max_parallel_workers = 4
160+
max_prepared_transactions = 100
161+
max_locks_per_transaction = 256
162+
max_pred_locks_per_transaction = 256
163+
164+
# WAL Settings
165+
wal_level = logical
166+
max_wal_senders = 20
167+
max_replication_slots = 0
168+
max_wal_size = 512MB
169+
min_wal_size = 80MB
170+
171+
# Extensions
172+
shared_preload_libraries = 'pg_stat_statements'
173+
174+
# Logging
175+
logging_collector = on
176+
log_destination = 'stderr'
177+
log_directory = 'log'
178+
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
179+
log_rotation_age = 1d
180+
log_rotation_size = 100MB
181+
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
182+
log_timezone = 'UTC'
183+
184+
# Performance
185+
random_page_cost = 1.1
186+
effective_io_concurrency = 200
187+
188+
# Statistics
189+
track_io_timing = on
190+
track_functions = pl
191+
192+
# Locale
193+
datestyle = 'iso, mdy'
194+
timezone = 'UTC'
195+
lc_messages = 'en_US.UTF-8'
196+
lc_monetary = 'en_US.UTF-8'
197+
lc_numeric = 'en_US.UTF-8'
198+
lc_time = 'en_US.UTF-8'
199+
default_text_search_config = 'pg_catalog.english'
140200
EOF
141201

202+
# Substitute the port variable
203+
sudo -u postgres sed -i "s/\${PG_PORT}/${PG_PORT}/g" "${DATA_DIR}/postgresql.conf"
204+
log "postgresql.conf created"
205+
142206
# Configure pg_hba.conf for local access only
143207
sudo -u postgres tee "${DATA_DIR}/pg_hba.conf" > /dev/null << EOF
144208
# TYPE DATABASE USER ADDRESS METHOD
@@ -180,8 +244,8 @@ log "Systemd service created"
180244

181245
# 7. Start PostgreSQL cluster
182246
log "Starting PostgreSQL cluster..."
183-
sudo systemctl enable "${SERVICE_NAME}"
184-
sudo systemctl start "${SERVICE_NAME}"
247+
sudo systemctl enable "${SERVICE_NAME}" || die "Failed to enable systemd service"
248+
sudo systemctl start "${SERVICE_NAME}" || die "Failed to start systemd service"
185249

186250
# Wait for PostgreSQL to be ready
187251
log "Waiting for PostgreSQL to be ready..."

0 commit comments

Comments
 (0)