Files
mesh-sdk/go/delivery/published_packages.go
T

127 lines
5.6 KiB
Go

package delivery
import (
"encoding/json"
"fmt"
"slices"
"strings"
"time"
)
// The published mesh packages (novox/hq ADR 0593 §2): the indexer alone reads each mesh Go module's package digests
// from the forge's listing at its merge and writes, in its own state, the latest published version of the module and
// the content digest of each package in it. The builder, the checker and the build modules read this state; no other
// part computes a package's version or digest. This is the contract only: the key, the value and its checks. How a
// digest is computed is the indexer's own.
const (
// PackagesState is the name of the indexer's state of published packages, as its manifest declares it, and
// PackagesBucket its bucket: one key per mesh Go module (PackagesKey).
PackagesState = "packages"
PackagesBucket = IndexerModule + "_" + PackagesState
// PackagesHistory is the revisions the packages state keeps per key: the module's newest publications.
PackagesHistory = 5
)
// PackagesKey is the key, in PackagesBucket, of a mesh Go module (its module path, such as
// git.novox.be/novox/mesh-sdk/go): the path as a key part (EscapeName). It refuses a path that is no mesh Go module's
// (PackageRepository).
func PackagesKey(module string) (string, error) {
if _, _, ok := PackageRepository(module); !ok {
return "", fmt.Errorf("delivery: %q is no mesh Go module's path (it starts with %s)", module, ForgeModulePrefix)
}
return EscapeName(module), nil
}
// PublishedPackages is the value of a packages key: a mesh Go module as its latest trunk merge published it. Module is
// its module path, Repository (`<owner>/<name>`) and Dir its repository and its directory there (RootPath for the
// root), Commit the merge commit (the version a build pins, StepAsk.Packages), Requires the digest of its go.mod and
// go.sum together, and Packages the content digest of each package of the module (its files, tests and sub-packages
// excluded), by import path, sorted and each once. Packages is a list, never a map keyed by import path: a state value
// refuses a field name ending in a credential word, and a package such as .../internal/token is one (issue 1930).
type PublishedPackages struct {
V int `json:"v"`
Module string `json:"module"`
Repository string `json:"repository"`
Dir string `json:"dir"`
Commit string `json:"commit"`
Requires string `json:"requires"`
Packages []PackageDigest `json:"packages"`
At time.Time `json:"at"`
}
// DigestOf is the content digest of the package at import path pkg, and whether p holds it.
func (p PublishedPackages) DigestOf(pkg string) (string, bool) {
i, found := slices.BinarySearchFunc(p.Packages, pkg, func(d PackageDigest, s string) int { return strings.Compare(d.Package, s) })
if !found {
return "", false
}
return p.Packages[i].Digest, true
}
func (p PublishedPackages) check() error {
repository, _, ok := PackageRepository(p.Module)
switch {
case !ok:
return fmt.Errorf("delivery: %q is no mesh Go module's path (it starts with %s)", p.Module, ForgeModulePrefix)
case !SameRepository(p.Repository, repository):
return fmt.Errorf("delivery: the module %s is published from %s, and the value names the repository %q", p.Module, repository, p.Repository)
case p.Dir == "":
return fmt.Errorf("delivery: the published module %s names no directory (%q for the repository's root)", p.Module, RootPath)
case !askCommitOK.MatchString(p.Commit):
return fmt.Errorf("delivery: the published module %s names the commit %q, which is no full commit", p.Module, p.Commit)
case p.Requires == "":
return fmt.Errorf("delivery: the published module %s names no digest of its requirements", p.Module)
case p.At.IsZero():
return fmt.Errorf("delivery: the published module %s names no time", p.Module)
}
for i, d := range p.Packages {
if d.Package != p.Module && !strings.HasPrefix(d.Package, p.Module+"/") {
return fmt.Errorf("delivery: the package %q is not in the module %s", d.Package, p.Module)
}
if d.Digest == "" {
return fmt.Errorf("delivery: the package %s names no digest", d.Package)
}
if i > 0 && p.Packages[i-1].Package >= d.Package {
return fmt.Errorf("delivery: the packages of %s are not sorted and each once (%s after %s)", p.Module, d.Package, p.Packages[i-1].Package)
}
}
return nil
}
// EncodePublishedPackages is p as JSON at Version; it refuses what a reader could not take (check).
func EncodePublishedPackages(p PublishedPackages) ([]byte, error) {
if err := p.check(); err != nil {
return nil, err
}
p.V = Version
return json.Marshal(p)
}
// DecodePublishedPackages reads a packages value; it refuses an unknown version (ErrUnknownVersion) and what
// EncodePublishedPackages refuses.
func DecodePublishedPackages(data []byte) (PublishedPackages, error) {
var p PublishedPackages
if err := decode(data, &p); err != nil {
return PublishedPackages{}, err
}
if err := p.check(); err != nil {
return PublishedPackages{}, err
}
return p, nil
}
// CheckImports refuses an import set (KeptInputs2.Imports, BuildAnswer.Imports, BuildLogEntry.Imports) that holds a
// path that is no mesh package's, or is not sorted and each once.
func CheckImports(imports []string) error {
for i, pkg := range imports {
if _, _, ok := PackageRepository(pkg); !ok || strings.ContainsAny(pkg, " \t\n@") {
return fmt.Errorf("delivery: the import %q is no mesh package's path (it starts with %s)", pkg, ForgeModulePrefix)
}
if i > 0 && imports[i-1] >= pkg {
return fmt.Errorf("delivery: the imports are not sorted and each once (%s after %s)", pkg, imports[i-1])
}
}
return nil
}