package catalogue import ( "fmt" "sort" "strings" ) // Zones: names a module answers itself (novox/hq ADR 0199). // // The mesh's resolver holds each node's internal domain and nothing else (ADR 0191, 0194). A module // whose names are its own — the lab's scenario machines, known only while a scenario runs — declares // the zone it answers and the listen that answers it; the controller hands the resolver's holder every // zone with the declaring node's private address and the port that listen is published on, and the // holder forwards the zone there. **A definition names no address** (ADR 0112): the zone is a setting, // the listen is the module's own, and where they are is the mesh's fact. // Zone is the manifest's declaration that a module answers the names in one zone. type Zone struct { // Name is the zone: a label or a dotted name, normally `${setting:}`, so the operator chooses // it and the definition does not. Name string `json:"name"` // Listen names one of the module's listens: the DNS answerer for the zone. Listen string `json:"listen"` } // ZoneAt is a declared zone where the mesh placed it: what the resolver's holder forwards, and where. type ZoneAt struct { Zone string Node string Module string Address string Port int } // zoneProblems is what is wrong with a module's zone declaration on its own, before any node. func zoneProblems(m Manifest) []string { if m.Zone == nil { return nil } var problems []string if strings.TrimSpace(m.Zone.Name) == "" { problems = append(problems, fmt.Sprintf("%s declares a zone with no name", m.Module)) } if !m.hasListen(m.Zone.Listen) { problems = append(problems, fmt.Sprintf( "%s declares zone %q answered by listen %q, and has no listen of that name", m.Module, m.Zone.Name, m.Zone.Listen)) } return problems } func (m Manifest) hasListen(name string) bool { if name == "" { return false } for _, l := range m.Listens { if l.Name == name { return true } } return false } // ZoneOn is one module's zone as one node places it: the name settled from the node's settings, the // port its answering listen is published on there. Nothing when the module declares no zone. func ZoneOn(m Manifest, layers []Layer, published map[int]int, node, address string) (*ZoneAt, error) { if m.Zone == nil { return nil, nil } settled, err := Settle(map[string]any{"zone": m.Zone.Name}, layers) if err != nil { return nil, fmt.Errorf("%s's zone on %s: %w", m.Module, node, err) } zone := strings.Trim(strings.ToLower(fmt.Sprint(settled["zone"])), ".") var port int for _, l := range m.Listens { if l.Name == m.Zone.Listen { port = l.Port if at, given := published[l.Port]; given { port = at } } } return &ZoneAt{Zone: zone, Node: node, Module: m.Module, Address: address, Port: port}, nil } // ZonesProblems is what the mesh refuses about its zones together: one zone declared twice, a zone // that is the mesh's suffix or under it, a zone that is a node's public domain or under one. A module // may not shadow names the mesh's resolver or the public DNS answers. func ZonesProblems(zones []ZoneAt, suffix string, publicDomains []string) []string { var problems []string under := func(zone, domain string) bool { domain = strings.Trim(strings.ToLower(domain), ".") return domain != "" && (zone == domain || strings.HasSuffix(zone, "."+domain)) } seen := map[string]ZoneAt{} for _, z := range zones { if other, twice := seen[z.Zone]; twice && (other.Node != z.Node || other.Module != z.Module) { problems = append(problems, fmt.Sprintf("zone %q is declared by %s on %s and by %s on %s; one module answers a zone", z.Zone, other.Module, other.Node, z.Module, z.Node)) } seen[z.Zone] = z if under(z.Zone, suffix) { problems = append(problems, fmt.Sprintf("%s on %s declares zone %q, which is the mesh's own suffix or under it", z.Module, z.Node, z.Zone)) } for _, d := range publicDomains { if under(z.Zone, d) { problems = append(problems, fmt.Sprintf("%s on %s declares zone %q, which is the public domain %q or under it", z.Module, z.Node, z.Zone, d)) } } } sort.Strings(problems) return problems }