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>
79 lines
2.5 KiB
Go
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
|
|
}
|