Consolidate 16 migrations into one with UUID columns for all entity
IDs. TEXT is kept only for polymorphic fields (audit_logs.actor_id,
resource_id) and template names. The id package now generates UUIDs
via google/uuid, with Format*/Parse* helpers for the prefixed wire
format (sb-{uuid}, usr-{uuid}, etc.). Auth context, services, and
handlers pass pgtype.UUID internally; conversion to/from prefixed
strings happens at API and RPC boundaries. Adds PlatformTeamID
(all-zeros UUID) for shared resources.
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
package lifecycle
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.omukk.dev/wrenn/sandbox/internal/db"
|
|
"git.omukk.dev/wrenn/sandbox/internal/id"
|
|
"git.omukk.dev/wrenn/sandbox/proto/hostagent/gen/hostagentv1connect"
|
|
)
|
|
|
|
// HostClientPool maintains a cache of Connect RPC clients keyed by host ID.
|
|
// Clients are created lazily on first access and evicted when a host is removed
|
|
// or goes unreachable. The pool is safe for concurrent use.
|
|
type HostClientPool struct {
|
|
mu sync.RWMutex
|
|
clients map[string]hostagentv1connect.HostAgentServiceClient
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewHostClientPool creates a new pool. The underlying HTTP client uses a
|
|
// 10-minute timeout to support long-running streaming operations.
|
|
func NewHostClientPool() *HostClientPool {
|
|
return &HostClientPool{
|
|
clients: make(map[string]hostagentv1connect.HostAgentServiceClient),
|
|
httpClient: &http.Client{Timeout: 10 * time.Minute},
|
|
}
|
|
}
|
|
|
|
// Get returns a Connect RPC client for the given host, creating one if necessary.
|
|
// address is the host agent address (ip:port or full URL). The scheme is added if absent.
|
|
func (p *HostClientPool) Get(hostID, address string) hostagentv1connect.HostAgentServiceClient {
|
|
p.mu.RLock()
|
|
c, ok := p.clients[hostID]
|
|
p.mu.RUnlock()
|
|
if ok {
|
|
return c
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
// Double-check after acquiring write lock.
|
|
if c, ok = p.clients[hostID]; ok {
|
|
return c
|
|
}
|
|
c = hostagentv1connect.NewHostAgentServiceClient(p.httpClient, EnsureScheme(address))
|
|
p.clients[hostID] = c
|
|
return c
|
|
}
|
|
|
|
// GetForHost is a convenience wrapper that extracts the address from a db.Host
|
|
// and returns an error if the host has no address recorded yet.
|
|
func (p *HostClientPool) GetForHost(h db.Host) (hostagentv1connect.HostAgentServiceClient, error) {
|
|
if h.Address == "" {
|
|
return nil, fmt.Errorf("host %s has no address", id.FormatHostID(h.ID))
|
|
}
|
|
return p.Get(id.FormatHostID(h.ID), h.Address), nil
|
|
}
|
|
|
|
// Evict removes the cached client for the given host, forcing a new client to be
|
|
// created on the next call to Get. Call this when a host's address changes or when
|
|
// a host is deleted.
|
|
func (p *HostClientPool) Evict(hostID string) {
|
|
p.mu.Lock()
|
|
delete(p.clients, hostID)
|
|
p.mu.Unlock()
|
|
}
|
|
|
|
// EnsureScheme adds "http://" if the address has no scheme.
|
|
func EnsureScheme(addr string) string {
|
|
if strings.HasPrefix(addr, "http://") || strings.HasPrefix(addr, "https://") {
|
|
return addr
|
|
}
|
|
return "http://" + addr
|
|
}
|