Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Agents poll AETHERFORGE_CLOUD_MAP_ENDPOINT to sync know_node gossip; deploy plans attach route_via DNS hints for standalone operator registries.
158 lines
4.0 KiB
Go
158 lines
4.0 KiB
Go
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
|
|
}
|