feat: add PawSQL docs examples and image CI
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m28s

This commit is contained in:
2026-09-15 18:49:26 -04:00
commit 865f7c26c9
25 changed files with 2811 additions and 0 deletions

89
internal/router/routes.go Normal file
View File

@@ -0,0 +1,89 @@
// Package router resolves incoming SNI names to PostgreSQL backends.
package router
import (
"context"
"errors"
"fmt"
"strings"
"github.com/barkstack/pawsql/internal/config"
)
var (
ErrUnknownHostname = errors.New("unknown database hostname")
ErrUnknownDatabase = errors.New("unknown configured database")
)
// Backend is a resolved PostgreSQL destination.
type Backend struct {
DatabaseName string
Address string
}
// BackendResolver permits future lifecycle-aware backend discovery.
type BackendResolver interface {
Resolve(context.Context, string) (Backend, error)
ResolveDatabase(context.Context, string) (Backend, error)
}
// ConnectionLeaseManager releases a backend lease when a proxied client session ends.
// Resolvers that do not manage lifecycle state may ignore this optional interface.
type ConnectionLeaseManager interface {
ReleaseConnection(string)
}
// TrafficMeter receives byte counts flowing through a proxied backend session.
// clientToBackend identifies the PostgreSQL client-to-server direction.
type TrafficMeter interface {
RecordTraffic(database string, clientToBackend bool, bytes int64)
}
// StaticResolver resolves routes loaded from a Barkfile.
type StaticResolver struct {
hostnameRoutes map[string]Backend
databaseRoutes map[string]Backend
}
// NewStaticResolver builds a resolver from typed configuration.
func NewStaticResolver(databases []config.DatabaseConfig) (*StaticResolver, error) {
hostnameRoutes := make(map[string]Backend, len(databases))
databaseRoutes := make(map[string]Backend, len(databases))
for _, database := range databases {
if database.Name == "" {
return nil, errors.New("database name is required")
}
if _, exists := databaseRoutes[database.Name]; exists {
return nil, fmt.Errorf("duplicate database name %q", database.Name)
}
backend := Backend{DatabaseName: database.Name, Address: database.Upstream}
databaseRoutes[database.Name] = backend
hostname := config.NormalizeHostname(database.Hostname)
if hostname == "" {
continue
}
if _, exists := hostnameRoutes[hostname]; exists {
return nil, fmt.Errorf("duplicate hostname %q", hostname)
}
hostnameRoutes[hostname] = backend
}
return &StaticResolver{hostnameRoutes: hostnameRoutes, databaseRoutes: databaseRoutes}, nil
}
// Resolve returns the exact case-insensitive hostname match, never a default route.
func (r *StaticResolver) Resolve(_ context.Context, hostname string) (Backend, error) {
backend, ok := r.hostnameRoutes[strings.ToLower(strings.TrimSuffix(strings.TrimSpace(hostname), "."))]
if !ok {
return Backend{}, fmt.Errorf("%w: %s", ErrUnknownHostname, hostname)
}
return backend, nil
}
// ResolveDatabase returns the exact configured PostgreSQL database match.
func (r *StaticResolver) ResolveDatabase(_ context.Context, database string) (Backend, error) {
backend, ok := r.databaseRoutes[database]
if !ok {
return Backend{}, fmt.Errorf("%w: %s", ErrUnknownDatabase, database)
}
return backend, nil
}