package inventory import ( "context" "sort" "strings" "github.com/novox/mesh-controller/internal/catalogue" ) // The kinds of edge in the catalogue's one dependency relation (novox/hq ADR 0162). const ( // EdgeStandsOn: the module's artifact is built on the other's. EdgeStandsOn = "stands-on" // EdgePackages: the module's build reads the other's repository. EdgePackages = "packages" // EdgeBuiltBy: the module is built by the holder of the build-machine seat. EdgeBuiltBy = "built-by" // EdgeDeclared: the manifest's own `build.on`. EdgeDeclared = "declared" ) // Edge is one dependency: From depends on To, in the way Kind says. type Edge struct { From string `json:"from"` To string `json:"to"` Kind string `json:"kind"` } // Dependencies is the catalogue's dependency relation, whole: every module the mesh holds, with // an edge to each module it depends on and the kind of dependency on the edge. One answer, so // nothing else computes an edge (novox/hq ADR 0162) — the merge handler, `build --on` and the // overview all read this. // // Four sources, one relation: a manifest's `build.on`; the artifacts the latest build was made // against (an `artifact-store:///…` reference is an edge to that module); the repositories // the latest build read (an edge to the module whose source that is); and the build machine, which // every source-built module is built by. func (i *Inventory) Dependencies(ctx context.Context) ([]Edge, error) { entries, err := i.Catalogued(ctx) if err != nil { return nil, err } against, err := i.BuiltAgainst(ctx) if err != nil { return nil, err } read, err := i.ReadRepositories(ctx) if err != nil { return nil, err } return dependenciesOf(entries, against, read), nil } // dependenciesOf is Dependencies over what was read, so a test can hand it a catalogue. func dependenciesOf(entries []Entry, against map[string][]string, read map[string][]ReadRepository) []Edge { known := map[string]bool{} byRepository := map[string][]string{} var builders []string for _, e := range entries { name := e.Manifest.Module known[name] = true if r := repositoryKey(e.Source.Repository); r != "" { byRepository[r] = append(byRepository[r], name) } if e.Manifest.ClaimsSeat("mesh-build-machine") { builders = append(builders, name) } } seen := map[Edge]bool{} var out []Edge add := func(from, to, kind string) { if from == to || !known[to] { return } e := Edge{From: from, To: to, Kind: kind} if !seen[e] { seen[e] = true out = append(out, e) } } for _, e := range entries { name := e.Manifest.Module if e.Manifest.Build != nil { for _, on := range e.Manifest.Build.On { if on.Module != "" { add(name, on.Module, EdgeDeclared) } } } for _, ref := range against[name] { if rest, ok := strings.CutPrefix(ref, catalogue.ArtifactStoreScheme); ok { if base, _, found := strings.Cut(rest, "/"); found { add(name, base, EdgeStandsOn) } } } for _, r := range read[name] { for _, other := range byRepository[repositoryKey(r.Repository)] { add(name, other, EdgePackages) } } if e.Source.Repository != "" { for _, b := range builders { add(name, b, EdgeBuiltBy) } } } sort.Slice(out, func(a, b int) bool { if out[a].From != out[b].From { return out[a].From < out[b].From } if out[a].To != out[b].To { return out[a].To < out[b].To } return out[a].Kind < out[b].Kind }) return out } // repositoryKey is a repository as compared: lower-cased, without a trailing `.git`. func repositoryKey(repository string) string { return strings.ToLower(strings.TrimSuffix(strings.TrimSpace(repository), ".git")) }