An unrecorded index or a copy in progress can name a platform the records do not see, so only a person's collect, after its dry run, takes an index's platforms, and only once every kept index of each repository it touches was read. A copy missing a platform is copied again, and a copy a build holds again is no longer recorded as collected (review of #144).
212 lines
7.7 KiB
Go
212 lines
7.7 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// A base a build copies into the store is recorded where it is copied, and kept while a kept build stood
|
|
// on it (novox/hq ADR 0257).
|
|
|
|
func mirrorRef(repository string, n int) string {
|
|
return fmt.Sprintf("%s%s@sha256:%064x", catalogue.ArtifactStoreScheme, repository, n)
|
|
}
|
|
|
|
// builtOn records a successful build of a module that made one image and stood on these bases.
|
|
func builtOn(t *testing.T, inv *Inventory, id, module string, n int, mirrored []string, against ...string) {
|
|
t.Helper()
|
|
b := aBuild(id, module, "")
|
|
b.Made = []Artifact{{Name: "app", Kind: "image", Reference: ref(module, "app", n)}}
|
|
b.Against = against
|
|
b.Mirrored = mirrored
|
|
if err := inv.RecordBuild(context.Background(), b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func stateOf(t *testing.T, inv *Inventory, reference string) ArtifactState {
|
|
t.Helper()
|
|
states, err := inv.Artifacts(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range states {
|
|
if s.Reference == reference {
|
|
return s
|
|
}
|
|
}
|
|
return ArtifactState{Reference: reference, State: "unrecorded"}
|
|
}
|
|
|
|
func TestACopyAFailedBuildMadeIsRecordedAndNothingKeepsIt(t *testing.T) {
|
|
inv := fresh(t)
|
|
golang := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
failed := aBuild("f1", "", "the recipe fetched an undeclared image")
|
|
failed.Mirrored = []string{golang}
|
|
if err := inv.RecordBuild(context.Background(), failed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy only a failed build made reads %+v; want eligible", s)
|
|
}
|
|
}
|
|
|
|
func TestACopyIsKeptByTheKeptBuildsThatStoodOnItAndByNothingElse(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
holding(t, inv, "proxy")
|
|
old := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
current := mirrorRef("upstream/docker.io/library/golang", 2)
|
|
|
|
// The first build stood on the old copy; six more on the current one. The old copy falls out of
|
|
// the five kept builds; the current one is stood on by all five.
|
|
builtOn(t, inv, "b01", "proxy", 1, []string{old}, old)
|
|
for i := 2; i <= 7; i++ {
|
|
builtOn(t, inv, fmt.Sprintf("b%02d", i), "proxy", i, []string{current}, current)
|
|
}
|
|
if s := stateOf(t, inv, old); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy no kept build stood on reads %+v", s)
|
|
}
|
|
if s := stateOf(t, inv, current); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) {
|
|
t.Fatalf("a copy the kept builds stood on reads %+v", s)
|
|
}
|
|
|
|
// A definition naming the image upstream, by the digest every copy shares, keeps no copy.
|
|
m := catalogue.Manifest{Module: "proxy", Version: "2",
|
|
Build: &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "GO_BASE", Image: "golang@" + strings.SplitN(old, "@", 2)[1]}}}}
|
|
if err := inv.RegisterModule(ctx, m, Source{Repository: "https://forge.invalid/proxy.git"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, old); s.State != ArtifactEligible {
|
|
t.Fatalf("a definition naming the upstream image kept the old copy: %+v", s)
|
|
}
|
|
go_, err := inv.ToCollect(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !slices.Contains(go_, old) || slices.Contains(go_, current) {
|
|
t.Fatalf("offered %v to collect", go_)
|
|
}
|
|
|
|
// Once let go of, a copy stays let go of, even should a kept build still name it.
|
|
if err := inv.MarkCollected(ctx, []string{current}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, current); s.State != ArtifactCollected {
|
|
t.Fatalf("a collected copy reads %+v", s)
|
|
}
|
|
}
|
|
|
|
func TestAModulesBaseIsNotKeptAsACopy(t *testing.T) {
|
|
// A base that is another module's artifact is kept by that module's own builds, as before: the
|
|
// stood-on reason is a copy's alone.
|
|
inv := fresh(t)
|
|
holding(t, inv, "app")
|
|
base := ref("go-base", "build", 1)
|
|
b := aBuild("g1", "go-base", "")
|
|
b.Made = []Artifact{{Name: "build", Kind: "image", Reference: base}}
|
|
if err := inv.RecordBuild(context.Background(), b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
builtOn(t, inv, "a1", "app", 1, nil, base)
|
|
if s := stateOf(t, inv, base); s.State != ArtifactEligible {
|
|
t.Fatalf("a forgotten module's artifact read %+v because a build stood on it", s)
|
|
}
|
|
}
|
|
|
|
func TestAPersonRecordsACopyAndTheFirstRecordStands(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
copyRef := mirrorRef("route-proxy/on-go_base", 3)
|
|
if err := inv.RecordMirrored(ctx, "", "copied before copies were recorded", []string{copyRef}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordMirrored(ctx, "b9", "", []string{copyRef}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
known, err := inv.Mirrored(ctx)
|
|
if err != nil || !known[copyRef] {
|
|
t.Fatalf("recorded copies %v %v", known, err)
|
|
}
|
|
var why string
|
|
if err := inv.store.Pool().QueryRow(ctx, `select why from artifact_mirrored where reference = $1`, copyRef).Scan(&why); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if why != "copied before copies were recorded" {
|
|
t.Fatalf("the first record did not stand: %q", why)
|
|
}
|
|
if s := stateOf(t, inv, copyRef); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy a person recorded reads %+v", s)
|
|
}
|
|
}
|
|
|
|
// The migration records the copies the build records already name: only a module's own on-<argument>
|
|
// repository, and only once, by the earliest build naming it.
|
|
func TestTheCopiesTheRecordsAlreadyNameAreRecorded(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
copyRef := mirrorRef("route-proxy/on-go_base", 1)
|
|
notACopy := ref("go-base", "build", 2)
|
|
builtOn(t, inv, "b1", "route-proxy", 1, nil, copyRef, notACopy)
|
|
builtOn(t, inv, "b2", "route-proxy", 2, nil, copyRef)
|
|
// The rows a mesh had before the table: written by builds that did not say what they copied.
|
|
if _, err := inv.store.Pool().Exec(ctx, `truncate artifact_mirrored`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sql, err := os.ReadFile("migrations/0081-a-mirrored-base-is-recorded.sql")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, backfill, _ := strings.Cut(string(sql), "insert into artifact_mirrored")
|
|
if _, err := inv.store.Pool().Exec(ctx, "insert into artifact_mirrored"+backfill); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var reference, build string
|
|
var n int
|
|
if err := inv.store.Pool().QueryRow(ctx, `select count(*) from artifact_mirrored`).Scan(&n); err != nil || n != 1 {
|
|
t.Fatalf("%d copies recorded (%v); want the one", n, err)
|
|
}
|
|
if err := inv.store.Pool().QueryRow(ctx, `select reference, build from artifact_mirrored`).Scan(&reference, &build); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reference != copyRef || build != "b1" {
|
|
t.Fatalf("recorded %s by %s", reference, build)
|
|
}
|
|
}
|
|
|
|
// A copy let go of and copied again by a later build is held again: its record is back, and the sweep
|
|
// decides about it as before.
|
|
func TestACopyLetGoOfAndCopiedAgainIsHeldAgain(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
holding(t, inv, "proxy")
|
|
golang := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
builtOn(t, inv, "b01", "proxy", 1, []string{golang}, golang)
|
|
if err := inv.MarkCollected(ctx, []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactCollected {
|
|
t.Fatalf("a let-go copy reads %+v", s)
|
|
}
|
|
builtOn(t, inv, "b02", "proxy", 2, []string{golang}, golang)
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) {
|
|
t.Fatalf("a copy copied again reads %+v; want kept again", s)
|
|
}
|
|
// A person's record never undoes a collection: only a build that copied it says it is there.
|
|
if err := inv.MarkCollected(ctx, []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordMirrored(ctx, "", "again", []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactCollected {
|
|
t.Fatalf("a person's record brought a collected copy back: %+v", s)
|
|
}
|
|
}
|