package deploy import ( "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" ) const cloudMapEnvEndpoint = "AETHERFORGE_CLOUD_MAP_ENDPOINT" type CloudMapRegistryDocument struct { Namespace string `json:"namespace"` Service string `json:"service"` UpdatedAt string `json:"updated_at"` Instances []CloudMapRegistryInstance `json:"instances"` } type CloudMapRegistryInstance struct { AgentID string `json:"agent_id"` DNSName string `json:"dns_name"` IP string `json:"ip"` FetchURL string `json:"fetch_url"` Port int `json:"port"` Healthy bool `json:"healthy"` } var cloudMapHTTPGet = func(url string) ([]byte, error) { client := &http.Client{Timeout: 12 * time.Second} resp, err := client.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("cloud map registry HTTP %d", resp.StatusCode) } return io.ReadAll(io.LimitReader(resp.Body, 1<<20)) } func SetCloudMapHTTPGetForTest(fn func(string) ([]byte, error)) { if fn == nil { cloudMapHTTPGet = func(url string) ([]byte, error) { client := &http.Client{Timeout: 12 * time.Second} resp, err := client.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("cloud map registry HTTP %d", resp.StatusCode) } return io.ReadAll(io.LimitReader(resp.Body, 1<<20)) } return } cloudMapHTTPGet = fn } func CloudMapEndpoint() string { return strings.TrimRight(strings.TrimSpace(os.Getenv(cloudMapEnvEndpoint)), "/") } func normalizeCloudMapRegistry(doc CloudMapRegistryDocument) CloudMapRegistryDocument { doc.Namespace = strings.TrimSpace(doc.Namespace) doc.Service = strings.TrimSpace(doc.Service) if doc.Namespace == "" { doc.Namespace = "prod.local" } if doc.Service == "" { doc.Service = "seeder" } return doc } func FetchCloudMapRegistry(endpoint string) (CloudMapRegistryDocument, error) { endpoint = strings.TrimRight(strings.TrimSpace(endpoint), "/") if endpoint == "" { return CloudMapRegistryDocument{}, fmt.Errorf("cloud map endpoint required") } body, err := cloudMapHTTPGet(endpoint) if err != nil { return CloudMapRegistryDocument{}, err } var doc CloudMapRegistryDocument if err := json.Unmarshal(body, &doc); err != nil { return CloudMapRegistryDocument{}, err } return normalizeCloudMapRegistry(doc), nil } func CloudMapKnowNodeRecords(doc CloudMapRegistryDocument) []FleetGossipRecord { doc = normalizeCloudMapRegistry(doc) var out []FleetGossipRecord seen := make(map[string]bool) for _, inst := range doc.Instances { if !inst.Healthy { continue } target := strings.TrimSpace(inst.AgentID) if target == "" { target = strings.TrimSpace(inst.DNSName) } if target == "" || seen[target] { continue } seen[target] = true out = append(out, FleetGossipRecord{ Kind: FleetGossipKnowNode, TargetAgentID: target, Healthy: true, }) } return out } func CloudMapLANSeederHints(doc CloudMapRegistryDocument) []LANSeederHint { doc = normalizeCloudMapRegistry(doc) var out []LANSeederHint for _, inst := range doc.Instances { if !inst.Healthy { continue } id := strings.TrimSpace(inst.AgentID) if id == "" { id = strings.TrimSpace(inst.DNSName) } if id == "" { continue } out = append(out, LANSeederHint{ AgentID: id, IP: strings.TrimSpace(inst.IP), LANFallbackURL: strings.TrimSpace(inst.FetchURL), }) } return out } func SyncCloudMapRegistry(endpoint string, gossipFn func([]FleetGossipRecord)) error { doc, err := FetchCloudMapRegistry(endpoint) if err != nil { return err } records := CloudMapKnowNodeRecords(doc) if len(records) > 0 && gossipFn != nil { gossipFn(records) } if hints := CloudMapLANSeederHints(doc); len(hints) > 0 { localIP, _ := PrimaryLocalIPv4() SetLANSeederHints(hints, localIP) } return nil }