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

103
cmd/pawsql/main.go Normal file
View File

@@ -0,0 +1,103 @@
package main
import (
"crypto/tls"
"errors"
"flag"
"fmt"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"github.com/barkstack/pawsql/internal/config"
"github.com/barkstack/pawsql/internal/postgres"
"github.com/barkstack/pawsql/internal/router"
"github.com/barkstack/pawsql/internal/server"
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
if err := run(os.Args[1:], logger); err != nil {
logger.Error("pawsql failed", "error", err)
os.Exit(1)
}
}
func run(args []string, logger *slog.Logger) error {
validateOnly, configPath, err := parseArguments(args)
if err != nil {
return err
}
cfg, err := config.ParseFile(configPath)
if err != nil {
return err
}
if err := cfg.Validate(); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
certificate, err := tls.LoadX509KeyPair(cfg.TLS.CertFile, cfg.TLS.KeyFile)
if err != nil {
return fmt.Errorf("load TLS certificate and key: %w", err)
}
if validateOnly {
logger.Info("configuration is valid", "config", configPath)
return nil
}
staticResolver, err := router.NewStaticResolver(cfg.Databases)
if err != nil {
return fmt.Errorf("build route resolver: %w", err)
}
resolver := postgres.NewResolver(staticResolver, cfg.Databases, postgres.NewProvisioner(logger))
routingServer, err := server.New(&tls.Config{Certificates: []tls.Certificate{certificate}}, resolver, logger)
if err != nil {
return err
}
listener, err := net.Listen("tcp", cfg.Listen)
if err != nil {
return fmt.Errorf("listen on %s: %w", cfg.Listen, err)
}
logger.Info("pawsql listening", "address", listener.Addr().String())
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(signals)
serveErrors := make(chan error, 1)
go func() { serveErrors <- routingServer.Serve(listener) }()
select {
case received := <-signals:
logger.Info("shutdown signal received", "signal", received.String())
if err := routingServer.Shutdown(); err != nil && !errors.Is(err, net.ErrClosed) {
return fmt.Errorf("stop listener: %w", err)
}
if err := <-serveErrors; err != nil {
return err
}
routingServer.Wait()
logger.Info("pawsql shutdown complete")
return nil
case err := <-serveErrors:
return err
}
}
func parseArguments(args []string) (validateOnly bool, configPath string, err error) {
if len(args) > 0 && args[0] == "validate" {
validateOnly = true
args = args[1:]
}
flags := flag.NewFlagSet("pawsql", flag.ContinueOnError)
flags.SetOutput(os.Stderr)
configPath = "Barkfile"
flags.StringVar(&configPath, "config", configPath, "path to Barkfile")
if err := flags.Parse(args); err != nil {
return false, "", err
}
if flags.NArg() != 0 {
return false, "", fmt.Errorf("unexpected arguments: %v", flags.Args())
}
return validateOnly, configPath, nil
}