Files
mc-proxy/internal/db/routes.go
Kyle Isom 28321e22f4 Make AddRoute idempotent (upsert instead of reject duplicates)
AddRoute now updates an existing route if one already exists for the
same (listener, hostname) pair, instead of returning AlreadyExists.
This makes repeated deploys idempotent — the MCP agent can register
routes on every deploy without needing to remove them first.

- DB: INSERT ... ON CONFLICT DO UPDATE (SQLite upsert)
- In-memory: overwrite existing route unconditionally
- gRPC: error code changed from AlreadyExists to Internal (for real DB errors)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:01:45 -07:00

79 lines
2.5 KiB
Go

package db
import "fmt"
// Route is a database route record.
type Route struct {
ID int64
ListenerID int64
Hostname string
Backend string
Mode string // "l4" or "l7"
TLSCert string
TLSKey string
BackendTLS bool
SendProxyProtocol bool
}
// ListRoutes returns all routes for a listener.
func (s *Store) ListRoutes(listenerID int64) ([]Route, error) {
rows, err := s.db.Query(
`SELECT id, listener_id, hostname, backend, mode, tls_cert, tls_key, backend_tls, send_proxy_protocol
FROM routes WHERE listener_id = ? ORDER BY hostname`,
listenerID,
)
if err != nil {
return nil, fmt.Errorf("querying routes: %w", err)
}
defer func() { _ = rows.Close() }()
var routes []Route
for rows.Next() {
var r Route
if err := rows.Scan(&r.ID, &r.ListenerID, &r.Hostname, &r.Backend,
&r.Mode, &r.TLSCert, &r.TLSKey, &r.BackendTLS, &r.SendProxyProtocol); err != nil {
return nil, fmt.Errorf("scanning route: %w", err)
}
routes = append(routes, r)
}
return routes, rows.Err()
}
// CreateRoute inserts or updates a route and returns its ID. If a route
// for the same (listener_id, hostname) already exists, it is updated
// with the new values (upsert), making the operation idempotent.
func (s *Store) CreateRoute(listenerID int64, hostname, backend, mode, tlsCert, tlsKey string, backendTLS, sendProxyProtocol bool) (int64, error) {
result, err := s.db.Exec(
`INSERT INTO routes (listener_id, hostname, backend, mode, tls_cert, tls_key, backend_tls, send_proxy_protocol)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(listener_id, hostname) DO UPDATE SET
backend = excluded.backend,
mode = excluded.mode,
tls_cert = excluded.tls_cert,
tls_key = excluded.tls_key,
backend_tls = excluded.backend_tls,
send_proxy_protocol = excluded.send_proxy_protocol`,
listenerID, hostname, backend, mode, tlsCert, tlsKey, backendTLS, sendProxyProtocol,
)
if err != nil {
return 0, fmt.Errorf("inserting route: %w", err)
}
return result.LastInsertId()
}
// DeleteRoute deletes a route by listener ID and hostname.
func (s *Store) DeleteRoute(listenerID int64, hostname string) error {
result, err := s.db.Exec(
"DELETE FROM routes WHERE listener_id = ? AND hostname = ?",
listenerID, hostname,
)
if err != nil {
return fmt.Errorf("deleting route: %w", err)
}
n, _ := result.RowsAffected()
if n == 0 {
return fmt.Errorf("route %q not found on listener %d", hostname, listenerID)
}
return nil
}