package catalogue import ( "fmt" "regexp" "sort" "strings" ) // What a module may say about the machine it is running on. // // **A module cannot know where it will be assigned, and sometimes it must say so anyway.** Every // other name in a declaration is either the module's own — which it wrote — or something it // requires, which arrives as a binding. The machine underneath is neither: it is chosen when the // module is assigned, long after the manifest was written, and until now nothing carried it into a // file. // // The case that found this is a certificate authority inside the mesh (novox/hq ADR 0066). A proxy // reaches it at the address the mesh handed over, `.internal` — so the authority's own // certificate has to be issued for that name, or the first thing that happens is the proxy refusing // to talk to it. The authority is the one thing that cannot be told its name by a binding: it // provides, it does not require. Written as a literal it would be a manifest carrying one // deployment's machine name, which is the shape [ADR 0066] exists to remove. // // Three facts, all the mesh's own vocabulary — the same `node` and `at` a contribution already // carries, and the address behind `at`, for software that takes an address and not a name. The // case that found the third is a resolver pointing the container runtime at itself: the runtime's // list of resolvers is addresses, because a name there would have to be resolved by the resolver // it names. Nothing about what a machine is *for*: that would be the mesh learning what a module // means, which it does not do. // ofMachine is where a module says a fact about the machine underneath it belongs: // ${machine:}. var ofMachine = regexp.MustCompile(`\$\{machine:([a-z0-9][a-z0-9_-]*)\}`) // machineUsed are the keys a file's content asks for, first appearance first. func machineUsed(content string) []string { var used []string seen := map[string]bool{} for _, m := range ofMachine.FindAllStringSubmatch(content, -1) { if key := m[1]; !seen[key] { seen[key] = true used = append(used, key) } } return used } // machineFacts is what a module may name about the machine it was assigned to. // // `at` is absent rather than empty when the machine is not on the private network. A module asking // to be reached at an address that does not exist is a misconfiguration, and it is said here — // where the module and the machine are both named — rather than discovered later as a certificate // nobody can verify. // // `address` is what `at` resolves to, read from the names the control plane composed — the same map // the hosts file and the resolver's wildcards are written from, so a file naming the machine's // address and the file every other machine reaches it by cannot disagree. Absent, like `at`, when // the machine is off the network or the mesh has not placed it. func machineFacts(r Resolution, names map[string]string) map[string]string { out := map[string]string{"name": r.Node} if r.At != "" { out["at"] = r.At if address := names[r.At]; address != "" { out["address"] = address } } return out } // machineInto replaces a file's ${machine:…} placeholders with what the mesh knows about the // machine the module was assigned to. // // A key the mesh does not hold is refused, for the same reason a binding's is: left alone, the // literal would be written into a configuration file and read as a value. func machineInto(resource map[string]any, facts map[string]string, module string) error { if fmt.Sprint(resource["type"]) != "file" { return nil } content, ok := resource["content"].(string) if !ok { return nil } for _, key := range machineUsed(content) { value, has := facts[key] if !has { return fmt.Errorf( "%s has a file that says ${machine:%s}, and this machine says %s", module, key, orNothing(namesOfFacts(facts))) } resource["content"] = strings.ReplaceAll( content, fmt.Sprintf("${machine:%s}", key), value) content = resource["content"].(string) } return nil } func namesOfFacts(facts map[string]string) []string { out := make([]string, 0, len(facts)) for k := range facts { out = append(out, k) } sort.Strings(out) return out }