package catalogue import ( "fmt" "regexp" "sort" "strconv" "strings" ) // Telling a module where this machine put the holder of a seat. // // **The foundation's ports are the node's** (novox/hq ADR 0100): the port a foundation server was // given at genesis becomes that node's setting for the module that serves it, and every reader // follows the setting. Every consumer's binding did. The control plane's own connections did not // (04-ISSUES/102): they are written at genesis, before any module exists to bind to — full // connection strings, sealed, with the port inside — so when the node moved the store, the // control plane went on dialling where genesis had written and the mesh was headless. // // The control plane cannot open its own sealed connection to move the port, and it cannot bind // the store as a consumer would: a binding mints a credential, and what the control plane holds // is the foundation's superuser, made before the mesh. What it can do is read the node's settings // when it composes its own declaration — it is the thing that composes every other module's — and // say in its own environment which port this machine put the store at. // // So a module may ask about a **seat** (ADR 0079: a foundation seat is named after the server it // guards — `mesh-store`, `mesh-broker`). `${seat:mesh-store:5432}` is "the port this machine put // the holder of the mesh-store seat's 5432 at". Not a provision: nothing is required, nothing is // granted, no credential is minted. A seat is the mesh's own vocabulary for the store and the // broker, which is what makes this the control plane's way of naming them and not a way for a // module to reach a server it was not granted — the answer is a port number the mesh holds in the // clear, and the credential to use it is still the module's own to have. // // **The answer may be empty, and that is the one place a placeholder answers with nothing.** The // store and the broker are raised at genesis, before the mesh knows them as modules; a mesh raised // on the catalogue's own ports never gives them a setting at all. In both, the port genesis wrote // into the connection string is the right one, and the mesh has nothing to add. An empty answer // says exactly that, and what reads it — the control plane's `_PORT` twin — treats an empty value // as no value. Answering with the software's own port instead would override what genesis wrote // with a number the mesh never checked, on the one machine where that is a headless mesh. // ofSeat is where a module asks about a seat: ${seat::}. var ofSeat = regexp.MustCompile(`\$\{seat:([a-z0-9][a-z0-9-]*):([0-9]+)\}`) // seatInto replaces a resource's ${seat:…} placeholders with where this machine put each seat's // holder — in a file's content, and in a value of a container's environment. The same two places // portInto fills, for the same reason: they are where a process reads a number from. func seatInto(resource map[string]any, module string, with Rendering) error { switch fmt.Sprint(resource["type"]) { case "file": content, ok := resource["content"].(string) if !ok || !ofSeat.MatchString(content) { return nil } filled, err := seatsFilledInto(content, fmt.Sprintf("%s has a file that", module), with) if err != nil { return err } resource["content"] = filled case "container": env, ok := resource["env"].(map[string]any) if !ok { return nil } named := make([]string, 0, len(env)) for key := range env { named = append(named, key) } sort.Strings(named) // A fresh map, and only when something changes — this map is the catalogue's, shared by // every node running the module (see portInto). var filled map[string]any for _, key := range named { written, ok := env[key].(string) if !ok || !ofSeat.MatchString(written) { continue } value, err := seatsFilledInto(written, fmt.Sprintf("%s's container %s sets %s to something that", module, resource["name"], key), with) if err != nil { return err } if filled == nil { filled = map[string]any{} for k, v := range env { filled[k] = v } } filled[key] = value } if filled != nil { resource["env"] = filled } } return nil } // seatsFilledInto answers every ${seat:…} in one written value. // // A port the seat's holder does not publish on this machine — or a seat nothing on it holds — // answers with nothing, for the reason the package comment gives. A port that is not one is // refused: it was written by a person and it is wrong. func seatsFilledInto(written, where string, with Rendering) (string, error) { for _, m := range ofSeat.FindAllStringSubmatch(written, -1) { seat, port := m[1], m[2] wanted, err := strconv.Atoi(port) if err != nil || wanted < 1 || wanted > 65535 { return "", fmt.Errorf("%s says ${seat:%s:%s}, and %s is not a port", where, seat, port, port) } answer := "" if at, known := with.Seats[seat][wanted]; known { answer = strconv.Itoa(at) } written = strings.ReplaceAll(written, m[0], answer) } return written, nil }