InstallationProblems judges every value the mesh acts on for a name under a public top-level domain
or a public address, with prose, the world's registries, resolvers and certificate authorities
exempt, and a name a resource means on purpose declared with its reason (names-on-purpose). Run by
module check and a catalogue-wide test, not yet at registration, while the declared list shrinks.
${setting:<key>} fills a file from the assignment's settings and is refused when nothing set it.
A build context may live on the git seat; the request carries the seat's clone base (novox/hq ADR
0112, ADR 0155, issues 122 and 134).
172 lines
6.0 KiB
Go
172 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// where a build's repository is (novox/hq ADR 0111).
|
|
//
|
|
// A repository is on the mesh's own forge, or it is anywhere else. The first is recorded as its path
|
|
// on the forge holding the git seat, and cloned from wherever that forge runs at the moment of
|
|
// building; the second is a URL, recorded and cloned exactly as given. The build machine is not told
|
|
// the difference — it is handed a URL either way — because only the control plane knows where the
|
|
// seat's holder runs.
|
|
|
|
// gitSeat is the seat a self-hosted repository lives on.
|
|
const gitSeat = "git"
|
|
|
|
// buildSource is where a build's repository is: a URL, or a path on a seat's holder.
|
|
type buildSource struct {
|
|
Repository string
|
|
Seat string
|
|
}
|
|
|
|
// String is the source as a person reads it, which for one on a seat is not the URL: the URL is a
|
|
// fact about where the forge happens to run today.
|
|
func (s buildSource) String() string {
|
|
if s.Seat == "" {
|
|
return s.Repository
|
|
}
|
|
return fmt.Sprintf("%s on the %s seat", s.Repository, s.Seat)
|
|
}
|
|
|
|
// onASeat refuses an address given as a path on the forge.
|
|
//
|
|
// **A URL here would be recorded as a path**, and then composed onto the forge's address as one —
|
|
// cloning `http://forge:3000/https://github.com/…`. Refused by what an address plainly looks like,
|
|
// not repaired: `--self` promises a path, and something that is not one is a mistake to name.
|
|
func onASeat(repository string) error {
|
|
if strings.Contains(repository, ":") || strings.HasPrefix(repository, "/") ||
|
|
strings.Trim(repository, "/") == "" {
|
|
return fmt.Errorf("--self takes the repository's path on the forge, such as novox/mesh-catalog, "+
|
|
"and %q is not one — without --self it is built from exactly what is given", repository)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// cloneFrom is the URL a build machine clones for a source.
|
|
//
|
|
// A URL is itself. A path on a seat is composed from the seat's holder as the mesh sees it now —
|
|
// the same view planning takes of every machine, so the forge a build clones from is the forge the
|
|
// mesh says holds the seat.
|
|
func cloneFrom(ctx context.Context, source buildSource) (string, error) {
|
|
if source.Seat == "" {
|
|
return source.Repository, nil
|
|
}
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
world, err := theRestOfTheMesh(ctx, open.inventory, shelf, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return clonedFromSeat(world, source.Seat, source.Repository)
|
|
}
|
|
|
|
// clonedFromSeat composes the clone URL for a repository on a seat's holder.
|
|
//
|
|
// **Refused, never defaulted, at every step that has no answer.** Nobody holding the seat is a mesh
|
|
// without a forge of its own: it builds from external repositories and must say so rather than fail
|
|
// to clone. A holder off the private network cannot be reached by any build machine. A holder that
|
|
// serves no scheme or port has nothing to compose from — a default port here would be the forge's
|
|
// address guessed, which is the thing this exists to stop.
|
|
func clonedFromSeat(world catalogue.World, seatName, repository string) (string, error) {
|
|
base, err := seatBase(world, seatName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path := strings.TrimSuffix(strings.Trim(repository, "/"), ".git")
|
|
return fmt.Sprintf("%s/%s.git", base, path), nil
|
|
}
|
|
|
|
// seatBase is `scheme://host:port` of a seat's holder as the mesh reaches it, for cloning.
|
|
func seatBase(world catalogue.World, seatName string) (string, error) {
|
|
seat, known := catalogue.SeatNamed(seatName)
|
|
if !known || seat.Delivers == "" {
|
|
return "", fmt.Errorf("%q is not a seat a repository can live on", seatName)
|
|
}
|
|
var holder *catalogue.Held
|
|
for i, h := range world.Held {
|
|
if h.Claim == seat.Name && h.Scope == seat.Scope {
|
|
holder = &world.Held[i]
|
|
break
|
|
}
|
|
}
|
|
if holder == nil {
|
|
return "", fmt.Errorf("nobody holds the %s seat, so nothing can be cloned from this mesh's "+
|
|
"forge — assign a module that claims it, or build from the repository's URL without --self",
|
|
seat.Name)
|
|
}
|
|
var provider *catalogue.Provider
|
|
for i, p := range world.Offered[seat.Delivers] {
|
|
if p.Node == holder.Node && p.Module == holder.Module {
|
|
provider = &world.Offered[seat.Delivers][i]
|
|
}
|
|
}
|
|
if provider == nil {
|
|
return "", fmt.Errorf("%s on %s holds the %s seat and offers no %q to clone from",
|
|
holder.Module, holder.Node, seat.Name, seat.Delivers)
|
|
}
|
|
if provider.At == "" {
|
|
return "", fmt.Errorf("%s on %s holds the %s seat and is not on the private network, so no "+
|
|
"build machine can reach it", holder.Module, holder.Node, seat.Name)
|
|
}
|
|
scheme, _ := provider.Serves["scheme"].(string)
|
|
port := servedPort(provider.Serves["port"])
|
|
if scheme == "" || port == "" {
|
|
return "", fmt.Errorf("%s on %s holds the %s seat and does not serve a scheme and a port for %q",
|
|
holder.Module, holder.Node, seat.Name, seat.Delivers)
|
|
}
|
|
return fmt.Sprintf("%s://%s:%s", scheme, provider.At, port), nil
|
|
}
|
|
|
|
// seatBases is the clone base of every seat a recipe's context may name, for a build request
|
|
// (novox/hq ADR 0155). A seat nobody holds is left out rather than refused here: the build may not
|
|
// name it at all, and if it does the builder refuses with the seat's name.
|
|
func seatBases(ctx context.Context) map[string]string {
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
world, err := theRestOfTheMesh(ctx, open.inventory, shelf, "")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
bases := map[string]string{}
|
|
for _, seatName := range []string{gitSeat} {
|
|
if base, err := seatBase(world, seatName); err == nil {
|
|
bases[seatName] = base
|
|
}
|
|
}
|
|
return bases
|
|
}
|
|
|
|
// servedPort is a served port as text, however the manifest and the node's settings carried it.
|
|
func servedPort(v any) string {
|
|
switch p := v.(type) {
|
|
case float64:
|
|
return strconv.Itoa(int(p))
|
|
case int:
|
|
return strconv.Itoa(p)
|
|
case string:
|
|
return p
|
|
}
|
|
return ""
|
|
}
|