Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/package_users.go
T

166 lines
5.3 KiB
Go

package index
import (
"fmt"
"maps"
"slices"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/views"
)
// PackageUsersTool lists, per mesh package, its users and the series each accepts (DLV-I8; ADR 0606 §4; design 32 *A
// package, its series, and the series its users accept*), so a user left on an older series is seen.
const PackageUsersTool = "indexer_package_users"
// PackageUser is one trunk module using a package: the series it accepts (Named when its manifest's `packages` names it,
// else the default), that series' newest published version ("" while none is), and the version its fingerprint pins.
type PackageUser struct {
Module string `json:"module"`
Accepts string `json:"accepts"`
Named bool `json:"named"`
Newest string `json:"newest"`
Pinned string `json:"pinned,omitempty"`
}
// PackageUses is one package of indexer_package_users: its series held in the packages state, the newest each published
// (by series), its Go module path for a Go package, and its users in module order.
type PackageUses struct {
Package string `json:"package"`
GoModule string `json:"go-module,omitempty"`
Series map[string]string `json:"series"`
Users []PackageUser `json:"users"`
}
// PackageUsersOf answers indexer_package_users from the indexer's own states (modules, manifests, packages), for one
// package or, with only empty, every package. A package is known by a series key of the packages state or by a name some
// trunk manifest's `packages` names. A trunk module uses it when its manifest names it, or, for a Go package, when its
// go.sum or its build's imports reach the package's Go module from another repository (a sibling by path is no user).
// A value that cannot be read is an error, never a user silently left out.
func PackageUsersOf(modules, manifests, packages views.Source, only string) ([]PackageUses, error) {
known := map[string]*PackageUses{}
get := func(name string) *PackageUses {
if known[name] == nil {
known[name] = &PackageUses{Package: name, Series: map[string]string{}, Users: []PackageUser{}}
}
return known[name]
}
keys, err := packages.Keys()
if err != nil {
return nil, fmt.Errorf("the packages state cannot be listed: %w", err)
}
for _, key := range keys {
if !IsSeriesKey(key) {
continue
}
e, ok, err := packages.Get(key)
if err != nil {
return nil, fmt.Errorf("packages.%s cannot be read: %w", key, err)
}
if !ok {
continue
}
s, err := DecodeSeriesPackage(e.Value)
if err != nil {
return nil, fmt.Errorf("packages.%s holds what is not a series: %w", key, err)
}
p := get(s.Name)
if s.GoModule != "" {
p.GoModule = s.GoModule
}
p.Series[s.Series] = ""
if s.Newest != nil {
p.Series[s.Series] = s.Newest.Version
}
}
type trunkMod struct {
mod meshcatalog.ModulesByModuleValue
manifest catalogue.Manifest
}
var mods []string
held := map[string]trunkMod{}
names, err := modules.Keys()
if err != nil {
return nil, fmt.Errorf("the modules state cannot be listed: %w", err)
}
for _, name := range names {
e, ok, err := modules.Get(name)
if err != nil {
return nil, fmt.Errorf("modules.%s cannot be read: %w", name, err)
}
if !ok {
continue
}
mod, err := meshcatalogclient.DecodeModule(e.Value)
if err != nil || mod.Manifest == "" {
continue // no manifest held for it
}
me, ok, err := manifests.Get(mod.Manifest)
if err != nil {
return nil, fmt.Errorf("the manifest of %s cannot be read: %w", name, err)
}
if !ok {
continue
}
kept, err := meshcatalogclient.DecodeManifest(me.Value)
if err != nil {
return nil, fmt.Errorf("the manifest of %s: %w", name, err)
}
m, err := catalogue.Decode([]byte(kept.Manifest))
if err != nil {
return nil, fmt.Errorf("the manifest of %s does not read: %w", name, err)
}
for pkg := range m.Packages {
get(pkg)
}
held[name], mods = trunkMod{mod, m}, append(mods, name)
}
slices.Sort(mods)
reaches := func(mod meshcatalog.ModulesByModuleValue, goModule string) bool {
if goModule == "" || mod.Inputs2 == nil {
return false
}
if repo, _, ok := trunk.PackageRepository(goModule); !ok || trunk.SameRepository(repo, mod.Repository) {
return false
}
for _, p := range append(slices.Clone(mod.Inputs2.Packages), mod.Inputs2.Imports...) {
if p == goModule || strings.HasPrefix(p, goModule+"/") {
return true
}
}
return false
}
var out []PackageUses
for _, name := range slices.Sorted(maps.Keys(known)) {
if only != "" && name != only {
continue
}
p := known[name]
for _, m := range mods {
t := held[m]
_, named := t.manifest.Packages[name]
if !named && !reaches(t.mod, p.GoModule) {
continue
}
accepts := t.manifest.AcceptedSeries(name)
u := PackageUser{Module: m, Accepts: accepts, Named: named, Newest: p.Series[accepts]}
if p.GoModule != "" {
u.Pinned = t.mod.Packages[p.GoModule]
}
p.Users = append(p.Users, u)
}
out = append(out, *p)
}
if only != "" && len(out) == 0 {
return nil, fmt.Errorf("no series of %s is held and no trunk manifest names it", only)
}
if out == nil {
out = []PackageUses{}
}
return out, nil
}