BREAKING CHANGE: resolve gRPC API proxy targets (machines) on server instead of client, needs upgrade to v0.20 (#247)

Co-authored-by: Pasha Sviderski <me@psviderski.name>
This commit is contained in:
Justin Bradford
2026-05-19 15:22:50 +10:00
committed by GitHub
co-authored by Pasha Sviderski
parent 03ff4cd51d
commit c95136eae6
29 changed files with 914 additions and 741 deletions
+108
View File
@@ -0,0 +1,108 @@
package proxy
import (
"context"
"fmt"
"slices"
"strings"
"github.com/psviderski/uncloud/internal/machine/api/pb"
)
// MachineTarget represents a resolved machine target.
type MachineTarget struct {
ID, Name, Addr string
}
// MachinesNotFoundError indicates that one or more requested machines were not found.
type MachinesNotFoundError struct {
NotFound []string
}
func (e *MachinesNotFoundError) Error() string {
if len(e.NotFound) == 1 {
return fmt.Sprintf("machine not found: %s", e.NotFound[0])
}
return fmt.Sprintf("machines not found: %s", strings.Join(e.NotFound, ", "))
}
// MachineMapper provides access to machine information in the cluster.
type MachineMapper interface {
// MapMachines resolves a list of machine names/IDs (or "*") to a list of machine targets.
// Returns MachinesNotFoundError if any requested machine is not found (except when "*" is used).
MapMachines(ctx context.Context, namesOrIDs []string) ([]MachineTarget, error)
}
// Store is the interface required by MachineMapper to access the cluster store.
type Store interface {
ListMachines(ctx context.Context) ([]*pb.MachineInfo, error)
}
// CorrosionMapper implements MachineMapper using the corrosion store.
type CorrosionMapper struct {
store Store
}
func NewCorrosionMapper(store Store) *CorrosionMapper {
return &CorrosionMapper{store: store}
}
func (m *CorrosionMapper) MapMachines(ctx context.Context, namesOrIDs []string) ([]MachineTarget, error) {
if len(namesOrIDs) == 0 {
return nil, fmt.Errorf("no machines specified")
}
machines, err := m.store.ListMachines(ctx)
if err != nil {
return nil, fmt.Errorf("list machines: %w", err)
}
allTargets := make([]MachineTarget, 0, len(machines))
for _, machine := range machines {
ip, err := machine.Network.ManagementIp.ToAddr()
if err != nil {
return nil, fmt.Errorf("invalid management IP for machine '%s' in store: %w", machine.Name, err)
}
allTargets = append(allTargets, MachineTarget{
ID: machine.Id,
Name: machine.Name,
Addr: ip.String(),
})
}
if slices.Contains(namesOrIDs, "*") {
if len(allTargets) == 0 {
return nil, fmt.Errorf("no machines in cluster")
}
return allTargets, nil
}
// Build a map for lookup (keyed by both ID and name)
targetByLookup := make(map[string]MachineTarget, len(allTargets)*2)
for _, t := range allTargets {
targetByLookup[t.ID] = t
targetByLookup[t.Name] = t
}
// Resolve each requested machine.
targets := make([]MachineTarget, 0, len(namesOrIDs))
var notFound []string
seenTarget := make(map[string]struct{}, len(namesOrIDs))
for _, nameOrID := range namesOrIDs {
if t, ok := targetByLookup[nameOrID]; ok {
if _, seen := seenTarget[t.ID]; !seen {
targets = append(targets, t)
seenTarget[t.ID] = struct{}{}
}
} else {
notFound = append(notFound, nameOrID)
}
}
if len(notFound) > 0 {
return nil, &MachinesNotFoundError{NotFound: notFound}
}
return targets, nil
}