package frombus import ( "fmt" "sort" "strconv" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/delivery" ) // Export is one node's placement file as the composition input the controller fills says it (novox/hq ADR 0555 ยง6, // issue 1126): the inverse of Input for what a person decides, so the controller's one read-only verb // `placement-export` answers, from its own store by its own code, exactly what the seed writes into the catalogue. // What the machine observes (its record) is not in it. func Export(in compose.Input, node string) (delivery.Placement, error) { var n *compose.Node for i := range in.Nodes { if in.Nodes[i].Name == node { n = &in.Nodes[i] } } if n == nil { return delivery.Placement{}, fmt.Errorf("frombus: the input has no node %q", node) } // At the version the placement file is read at, with a list of modules even when none is placed: the export is the // file the seed writes (cmd/placement-seed), and the reader refuses a value of version 0 or without its modules. p := delivery.Placement{V: delivery.Version, Node: node, Modules: []delivery.PlacedModule{}, Decided: delivery.NodeDecided{ Endpoint: n.Place.Endpoint, Site: n.Place.Site, Hub: n.Place.Hub, Address: n.Place.Address, PublicDomain: n.PublicDomain, Account: n.Account, AccountHome: n.AccountHome, AgentAccount: n.AgentAccount, AgentHome: n.AgentAccountHome, Adopted: n.Adopted, Taken: n.Taken}} // The tunnel found at the node's adoption: the one its private network took over, or, on the hub, the mesh's adopted // one whether or not the hub took it; and the names a person gave the hub's carried peers. if t, ok := in.Tunnels[node]; ok { rt := recordTunnelOf(t.Tunnel) p.Decided.Tunnel = &rt } else if in.Adopted != nil && in.Adopted.Hub == node { rt := recordTunnelOf(in.Adopted.Tunnel) p.Decided.Tunnel = &rt } if n.Place.Hub { for _, c := range in.CarriedPeers { if c.Named != "" { if p.Decided.PeerNames == nil { p.Decided.PeerNames = map[string]string{} } p.Decided.PeerNames[c.PublicKey] = c.Named } } } for _, module := range n.Assigned { m := delivery.PlacedModule{Module: module} for port, at := range n.Ports[module] { if m.Ports == nil { m.Ports = map[string]int{} } m.Ports[strconv.Itoa(port)] = at } for provision, to := range n.Bound[module] { if m.Bindings == nil { m.Bindings = map[string]delivery.Provider{} } m.Bindings[provision] = delivery.Provider{Node: to.Node, Module: to.Module} } // A module in the settings form keeps its config values in its layers, not in mesh-config: they are exported with // the placement. A module in the config form has mesh-config's values laid into its layer, which are not. settingsForm := !in.Catalogue[module].DeclaresConfig() for _, layer := range n.Settings[module] { if layer.From == catalogue.MeshWideLayer { m.HasMesh, m.MeshWide = true, layer.Placement if settingsForm && len(layer.Values) > 0 { m.MeshValues = layer.Values } } else { m.HasLayer, m.Layer = true, layer.Placement if settingsForm && len(layer.Values) > 0 { m.Values = layer.Values } } } p.Modules = append(p.Modules, m) } for provision, to := range n.Pins { if p.Pins == nil { p.Pins = map[string]delivery.Provider{} } p.Pins[provision] = delivery.Provider{Node: to.Node, Module: to.Module} } for _, h := range in.Holdings { if h.Node == node { p.Holds = append(p.Holds, delivery.Holding{Claim: h.Claim, Scope: h.Scope, Module: h.Module, Site: h.Site, Kind: h.Kind}) } } sort.SliceStable(p.Holds, func(i, j int) bool { if p.Holds[i].Claim != p.Holds[j].Claim { return p.Holds[i].Claim < p.Holds[j].Claim } return p.Holds[i].Module < p.Holds[j].Module }) return p, nil } func recordTunnelOf(t compose.Tunnel) delivery.RecordTunnel { out := delivery.RecordTunnel{Interface: t.Interface, Unit: t.Unit, Config: t.Config, Port: t.Port, MTU: t.MTU, Address: t.Address, Range: t.Range, PublicKey: t.PublicKey} for _, peer := range t.Peers { out.Peers = append(out.Peers, delivery.RecordTunnelPeer{PublicKey: peer.PublicKey, Address: peer.Address}) } return out }