Merge branch 'feat/47-G-build' into feat/47-G-build-watchman
This commit is contained in:
@@ -736,7 +736,9 @@ type CatalogEntry struct {
|
||||
Main string `json:"main,omitempty"`
|
||||
// the validated manifest whole, kept as an object by the entry's digest, so no size limit applies; read by anyone but the indexer through the entry, never as a manifest (design 47 G section 443, section 442)
|
||||
Manifest map[string]any `json:"manifest"`
|
||||
Module string `json:"module"`
|
||||
// the sha256: digest of the manifest's bytes as the forge answered them, set by the indexer at take-in and never computed again from the manifest whole; the module's value and the manifests key are this digest (design 47 G section 506)
|
||||
ManifestDigest string `json:"manifest-digest,omitempty"`
|
||||
Module string `json:"module"`
|
||||
// the manifest reads a node-engine must state to compose it, its templates' forms included, as the forge bridge read them at the merge (ADR 0614 §4, ADR 0632 §2)
|
||||
Needs []string `json:"needs,omitempty"`
|
||||
// why it has none
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestFixtures(t *testing.T) {
|
||||
err := json.Unmarshal(b, &v)
|
||||
return v, err
|
||||
}, subjectIs: nil},
|
||||
{name: "a catalog graph entry (xclip)", subject: "", given: "{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"inputs\":{\"fp-src\":41,\"from\":{\"bases\":[\"arch\"],\"locks\":{\"go.sum\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\"packages\":[\"git.novox.be/novox/mesh-sdk/go@v0.0.0-20261101000000-f469eb387a30\"],\"tree\":\"4b825dc642cb6eb9a060e54bf8d69288fbee4904\"},\"src\":41,\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}}},\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}", wire: "{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"inputs\":{\"fp-src\":41,\"from\":{\"bases\":[\"arch\"],\"locks\":{\"go.sum\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\"packages\":[\"git.novox.be/novox/mesh-sdk/go@v0.0.0-20261101000000-f469eb387a30\"],\"tree\":\"4b825dc642cb6eb9a060e54bf8d69288fbee4904\"},\"src\":41,\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}}},\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}",
|
||||
{name: "a catalog graph entry (xclip)", subject: "", given: "{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"inputs\":{\"fp-src\":41,\"from\":{\"bases\":[\"arch\"],\"locks\":{\"go.sum\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\"packages\":[\"git.novox.be/novox/mesh-sdk/go@v0.0.0-20261101000000-f469eb387a30\"],\"tree\":\"4b825dc642cb6eb9a060e54bf8d69288fbee4904\"},\"src\":41,\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}}},\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"manifest-digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}", wire: "{\"bases\":[\"arch\"],\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"inputs\":{\"fp-src\":41,\"from\":{\"bases\":[\"arch\"],\"locks\":{\"go.sum\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\"packages\":[\"git.novox.be/novox/mesh-sdk/go@v0.0.0-20261101000000-f469eb387a30\"],\"tree\":\"4b825dc642cb6eb9a060e54bf8d69288fbee4904\"},\"src\":41,\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}}},\"invokes\":[\"node-files.read\"],\"manifest\":{\"module\":\"xclip\",\"version\":\"1\"},\"manifest-digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"module\":\"xclip\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"v0.0.0-20261101000000-f469eb387a30\"},\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"module\":\"mesh-build-go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1,\"verdict\":\"accepted\"}",
|
||||
decode: func(b []byte) (any, error) {
|
||||
var v CatalogRevisionsByEntryDigestValue
|
||||
err := json.Unmarshal(b, &v)
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
"module": "xclip",
|
||||
"version": "1"
|
||||
},
|
||||
"manifest-digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"verdict": "accepted",
|
||||
"fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56",
|
||||
"inputs": {
|
||||
|
||||
@@ -1872,6 +1872,10 @@
|
||||
"type": "object",
|
||||
"description": "the validated manifest whole, kept as an object by the entry's digest, so no size limit applies; read by anyone but the indexer through the entry, never as a manifest (design 47 G section 443, section 442)"
|
||||
},
|
||||
"manifest-digest": {
|
||||
"type": "string",
|
||||
"description": "the sha256: digest of the manifest's bytes as the forge answered them, set by the indexer at take-in and never computed again from the manifest whole; the module's value and the manifests key are this digest (design 47 G section 506)"
|
||||
},
|
||||
"verdict": {
|
||||
"type": "string",
|
||||
"description": "its manifest's verdict: accepted, refused or held"
|
||||
|
||||
+2
@@ -247,6 +247,8 @@ export interface CatalogEntry {
|
||||
"main"?: string;
|
||||
/** the validated manifest whole, kept as an object by the entry's digest, so no size limit applies; read by anyone but the indexer through the entry, never as a manifest (design 47 G section 443, section 442) */
|
||||
"manifest": Record<string, unknown>;
|
||||
/** the sha256: digest of the manifest's bytes as the forge answered them, set by the indexer at take-in and never computed again from the manifest whole; the module's value and the manifests key are this digest (design 47 G section 506) */
|
||||
"manifest-digest"?: string;
|
||||
"module": string;
|
||||
/** the manifest reads a node-engine must state to compose it, its templates' forms included, as the forge bridge read them at the merge (ADR 0614 §4, ADR 0632 §2) */
|
||||
"needs"?: string[];
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
|
||||
"mesh-delivery-builder/internal/build"
|
||||
@@ -45,12 +46,14 @@ type reservations struct {
|
||||
bound time.Duration
|
||||
wake func(context.Context)
|
||||
logf func(string, ...any)
|
||||
// call asks a seat's verb: the git seat's tag (design 47 G §370).
|
||||
call toolCaller
|
||||
}
|
||||
|
||||
// newReservations makes the reserving over store; the released versions of a module are asked of the seat mesh-catalog's
|
||||
// verb versions through call (design 47 G §348), the only source numbering reads them from.
|
||||
func newReservations(store versions.ReserveStore, call toolCaller, bounds func() jetstream.KeyValue, found *findings, wake func(context.Context), logf func(string, ...any)) *reservations {
|
||||
rv := &reservations{bounds: bounds, found: found, bound: versions.UnreleasedBound, wake: wake, logf: logf}
|
||||
rv := &reservations{call: call, bounds: bounds, found: found, bound: versions.UnreleasedBound, wake: wake, logf: logf}
|
||||
rv.r = &versions.Reserver{Store: store, Filled: rv.filled.Load, ReleasedOf: func(_ context.Context, module string) ([]string, error) {
|
||||
raw, err := call("seat:"+meshcatalog.Seat+".versions", meshcatalog.VersionsInput{Module: module})
|
||||
if err != nil {
|
||||
@@ -73,8 +76,8 @@ var _ build.Reservations = (*reservations)(nil)
|
||||
|
||||
func (rv *reservations) Filled() bool { return rv.filled.Load() }
|
||||
|
||||
func (rv *reservations) Reserve(ctx context.Context, module, series string, artifacts []string) (versions.ReservedVersion, error) {
|
||||
return rv.r.Reserve(ctx, module, series, artifacts)
|
||||
func (rv *reservations) Reserve(ctx context.Context, module, series string, artifacts []string, src versions.Source) (versions.ReservedVersion, error) {
|
||||
return rv.r.Reserve(ctx, module, series, artifacts, src)
|
||||
}
|
||||
|
||||
// Built records the digests; once every artifact has one, the publish's bound starts (a key of the state bounds with its
|
||||
@@ -165,6 +168,13 @@ func (rv *reservations) heard(ctx context.Context, m jetstream.Msg) bool {
|
||||
rv.logf("%s holds what is no version-released, left: %v", m.Subject(), err)
|
||||
break
|
||||
}
|
||||
if err := rv.tagged(ctx, ev); err != nil {
|
||||
rv.logf("%v; heard again", err)
|
||||
if nakErr := m.Nak(); nakErr != nil {
|
||||
rv.logf("leaving %s for the bus to give again: %v", m.Subject(), nakErr)
|
||||
}
|
||||
return true
|
||||
}
|
||||
gone, err := rv.r.Released(ctx, ev.Module, ev.Version)
|
||||
if err != nil {
|
||||
rv.logf("%v; heard again", err)
|
||||
@@ -206,3 +216,30 @@ func (rv *reservations) dropped(ctx context.Context, module, series, by, why str
|
||||
rv.found.clear(ctx, versions.VersionUnreleased, meshconditionsclient.ScopeModule, module)
|
||||
return fmt.Sprintf("the reservation of %s %s is dropped (%s, %s)", module, series, by, why), nil
|
||||
}
|
||||
|
||||
// tagged writes the git tag of a released version (design 47 G §370): on the catalog's version-released, through the seat
|
||||
// git's verb tag, at the commit the reservation of that version was made from. A version with no reservation (a superseded
|
||||
// build's version is never released, and the key of a version released by another builder is gone) or one that names no
|
||||
// tag (a module with no Go package) is tagged nowhere. A tag the forge could not write is an error, the message heard again.
|
||||
func (rv *reservations) tagged(ctx context.Context, ev meshcatalog.VersionReleasedEvent) error {
|
||||
res, found, err := rv.r.Of(ctx, ev.Module, ev.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the reservation of %s %s cannot be read to tag it: %w", ev.Module, ev.Version, err)
|
||||
}
|
||||
if !found || res.Tag == "" || res.Commit == "" {
|
||||
return nil
|
||||
}
|
||||
raw, err := rv.call("seat:"+git.Seat+".tag", git.TagInput{Repository: res.Repository, Commit: res.Commit, Tag: res.Tag})
|
||||
if err != nil {
|
||||
return fmt.Errorf("the tag %s of %s is not written: %w", res.Tag, ev.Module, err)
|
||||
}
|
||||
var a git.TagAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return fmt.Errorf("the git seat answered what is no tag answer for %s: %w", res.Tag, err)
|
||||
}
|
||||
if a.Error != "" || !a.Performed {
|
||||
return fmt.Errorf("the tag %s of %s is not written: %s (%s)", res.Tag, ev.Module, a.Error, a.ErrorKind)
|
||||
}
|
||||
rv.logf("%s is tagged %s at %s", ev.Module, res.Tag, res.Commit)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
|
||||
"mesh-delivery-builder/internal/versions"
|
||||
@@ -85,14 +86,14 @@ func TestTheCatalogsWordsMoveTheReservations(t *testing.T) {
|
||||
}
|
||||
rv := newReservations(&memReserve{vals: map[string][]byte{}, revs: map[string]uint64{}}, call, func() jetstream.KeyValue { return bounds }, found,
|
||||
func(context.Context) { woke++ }, t.Logf)
|
||||
if _, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a"}); err == nil || rv.Filled() {
|
||||
if _, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a"}, versions.Source{}); err == nil || rv.Filled() {
|
||||
t.Fatalf("a version was reserved before versions-filled (%v)", err)
|
||||
}
|
||||
filled := &wakeMsg{subject: meshcatalog.SubjectVersionsFilled, data: []byte(`{"v":1}`)}
|
||||
if !rv.heard(ctx, filled) || !filled.acked || !rv.Filled() || woke != 1 {
|
||||
t.Fatalf("versions-filled: acked %v, filled %v, woke %d", filled.acked, rv.Filled(), woke)
|
||||
}
|
||||
res, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"})
|
||||
res, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, versions.Source{})
|
||||
if err != nil || res.Version != "1.4.3" {
|
||||
t.Fatalf("reserved %q (%v), want 1.4.3 after the catalog's 1.4.2", res.Version, err)
|
||||
}
|
||||
@@ -119,7 +120,7 @@ func TestTheCatalogsWordsMoveTheReservations(t *testing.T) {
|
||||
if all, _ := rv.r.All(ctx); len(all) != 0 || bounds.keys[boundKey] || len(st.vals) != 0 {
|
||||
t.Errorf("after version-released: reservations %v, bounds %v, findings %v; want none", all, bounds.keys, st.vals)
|
||||
}
|
||||
if _, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a"}); err != nil {
|
||||
if _, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a"}, versions.Source{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if answer, err := rv.dropped(ctx, "m", "1.4", "op", "never released", time.Unix(0, 0)); err != nil || !strings.Contains(answer, "dropped") {
|
||||
@@ -129,3 +130,64 @@ func TestTheCatalogsWordsMoveTheReservations(t *testing.T) {
|
||||
t.Error("a reservation that is gone was dropped again")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAReleasedVersionIsTaggedAtTheCommitItWasBuiltFrom: design 47 G §370 — the git tag go/vMAJOR.MINOR.PATCH is written through
|
||||
// the seat git's tag on the catalog's version-released, at the commit the version's reservation was made from; a module with no
|
||||
// Go package, and a version never released (a superseded build's), are tagged nowhere; a tag the forge did not write leaves
|
||||
// version-released to be heard again with the reservation standing.
|
||||
func TestAReleasedVersionIsTaggedAtTheCommitItWasBuiltFrom(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var tags []git.TagInput
|
||||
refuse := ""
|
||||
call := func(addr string, body any) (json.RawMessage, error) {
|
||||
if addr != "seat:git.tag" {
|
||||
return json.Marshal(meshcatalog.VersionsAnswer{})
|
||||
}
|
||||
tags = append(tags, body.(git.TagInput))
|
||||
return json.Marshal(git.TagAnswer{Performed: refuse == "", Error: refuse})
|
||||
}
|
||||
found := &findings{store: &memFindings{vals: map[string][]byte{}}, logf: t.Logf, now: func() time.Time { return time.Unix(0, 0) }}
|
||||
rv := newReservations(&memReserve{vals: map[string][]byte{}, revs: map[string]uint64{}}, call, func() jetstream.KeyValue { return &boundsKV{keys: map[string]bool{}} }, found, nil, t.Logf)
|
||||
rv.filled.Store(true)
|
||||
goPkg := versions.Source{Repository: "o/r", Commit: "c0ffee", GoPackage: true}
|
||||
if _, err := rv.Reserve(ctx, "pkg", "0.1", []string{"pkg:lib"}, goPkg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := rv.Reserve(ctx, "prog", "2.0", []string{"prog:bin"}, versions.Source{Repository: "o/p", Commit: "beef"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
release := func(module, version string) *wakeMsg {
|
||||
m := &wakeMsg{subject: meshcatalog.SubjectVersionReleased, data: []byte(`{"v":1,"module":"` + module + `","version":"` + version + `","source":1,"artifacts":[]}`)}
|
||||
rv.heard(ctx, m)
|
||||
return m
|
||||
}
|
||||
if m := release("prog", "2.0.0"); !m.acked || len(tags) != 0 {
|
||||
t.Fatalf("a module with no Go package: acked %v, tags %v; want none written", m.acked, tags)
|
||||
}
|
||||
refuse = "forge down"
|
||||
if m := release("pkg", "0.1.0"); !m.naked || m.acked {
|
||||
t.Fatalf("a tag the forge did not write: naked %v acked %v; want the release heard again", m.naked, m.acked)
|
||||
}
|
||||
if all, _ := rv.r.All(ctx); len(all) != 1 {
|
||||
t.Fatalf("the reservation went with an unwritten tag: %v", all)
|
||||
}
|
||||
refuse = ""
|
||||
tags = nil
|
||||
if m := release("pkg", "0.1.0"); !m.acked || len(tags) != 1 || tags[0] != (git.TagInput{Repository: "o/r", Commit: "c0ffee", Tag: "go/v0.1.0"}) {
|
||||
t.Fatalf("the release: acked %v, tags %+v; want go/v0.1.0 at c0ffee of o/r", m.acked, tags)
|
||||
}
|
||||
tags = nil
|
||||
if _, err := rv.Reserve(ctx, "pkg", "0.1", []string{"pkg:lib"}, goPkg); err != nil { // build A: 0.1.0 (the stub catalog holds none released)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := rv.Reserve(ctx, "pkg", "0.1", []string{"pkg:lib"}, versions.Source{Repository: "o/r", Commit: "b0b", GoPackage: true}); err != nil || v.Version != "0.1.1" { // build B supersedes A
|
||||
t.Fatalf("second build reserved %q (%v)", v.Version, err)
|
||||
}
|
||||
release("pkg", "0.1.0") // A's version is never released in fact; were it said, it is no reservation of the key
|
||||
if len(tags) != 0 {
|
||||
t.Fatalf("a superseded build's version was tagged: %+v", tags)
|
||||
}
|
||||
if release("pkg", "0.1.1"); len(tags) != 1 || tags[0].Commit != "b0b" {
|
||||
t.Fatalf("the newer build's version: tags %+v; want go/v0.1.1 at b0b", tags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/git v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -3,6 +3,7 @@ package build
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -148,7 +149,7 @@ type Reservations interface {
|
||||
// Filled says the catalog said versions-filled: nothing is reserved before it.
|
||||
Filled() bool
|
||||
// Reserve reserves module's next version in series for the artifacts (their references, <module>:<artifact>).
|
||||
Reserve(ctx context.Context, module, series string, artifacts []string) (versions.ReservedVersion, error)
|
||||
Reserve(ctx context.Context, module, series string, artifacts []string, src versions.Source) (versions.ReservedVersion, error)
|
||||
// Built records the digests of the artifacts built under the reserved version, by reference.
|
||||
Built(ctx context.Context, module, version string, digests map[string]string) error
|
||||
}
|
||||
@@ -184,12 +185,16 @@ func (b *Builder) waitFill(module string, m meshcatalog.ModulesByModuleValue) bo
|
||||
|
||||
// reserve reserves the version of the build that starts; a reservation that cannot be made is said and the build goes on
|
||||
// unreserved, as an unnumbered build does.
|
||||
func (b *Builder) reserve(ctx context.Context, module string, plan Plan) (versions.ReservedVersion, bool) {
|
||||
func (b *Builder) reserve(ctx context.Context, module string, m meshcatalog.ModulesByModuleValue, plan Plan) (versions.ReservedVersion, bool) {
|
||||
refs := make([]string, 0, len(plan.Tasks))
|
||||
for _, t := range plan.Tasks {
|
||||
refs = append(refs, module+":"+t.Artifact)
|
||||
}
|
||||
res, err := b.Reserved.Reserve(ctx, module, plan.Series, refs)
|
||||
src := versions.Source{Repository: m.Repository, Commit: m.Commit}
|
||||
for _, t := range plan.Tasks {
|
||||
src.GoPackage = src.GoPackage || (t.Kind == catalogue.ArtifactPackage && t.Language == "go")
|
||||
}
|
||||
res, err := b.Reserved.Reserve(ctx, module, plan.Series, refs, src)
|
||||
if err != nil {
|
||||
b.logf("%s: no version is reserved for this build: %v", module, err)
|
||||
return versions.ReservedVersion{}, false
|
||||
|
||||
@@ -23,7 +23,7 @@ type fakeReservations struct {
|
||||
|
||||
func (f *fakeReservations) Filled() bool { return f.filled }
|
||||
|
||||
func (f *fakeReservations) Reserve(_ context.Context, module, series string, artifacts []string) (versions.ReservedVersion, error) {
|
||||
func (f *fakeReservations) Reserve(_ context.Context, module, series string, artifacts []string, _ versions.Source) (versions.ReservedVersion, error) {
|
||||
f.reserved, f.series = append(f.reserved, artifacts), append(f.series, series)
|
||||
return versions.ReservedVersion{Module: module, Series: series, Version: series + ".5"}, nil
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB
|
||||
var reserved versions.ReservedVersion
|
||||
hasReserved := false
|
||||
if reserving && cl.Build.State != builds.BuildDone {
|
||||
reserved, hasReserved = b.reserve(ctx, module, plan)
|
||||
reserved, hasReserved = b.reserve(ctx, module, m, plan)
|
||||
}
|
||||
var result builds.BuilderBuild
|
||||
if nothing {
|
||||
|
||||
@@ -49,6 +49,18 @@ type ReservedVersion struct {
|
||||
// Published are the artifacts a registry seat said published at this version with the built digest.
|
||||
Published map[string]bool `json:"published,omitempty"`
|
||||
At time.Time `json:"at"`
|
||||
// Repository and Commit are what the build was made from, and Tag the git tag its release is written as (a Go
|
||||
// package's, GoTagOf): empty for a module with no Go package (design 47 G §370).
|
||||
Repository string `json:"repository,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// Source is what a build reserving a version is made from (design 47 G §370): the repository and commit the git tag is
|
||||
// written at, and whether the module has a Go package, whose version is tagged.
|
||||
type Source struct {
|
||||
Repository, Commit string
|
||||
GoPackage bool
|
||||
}
|
||||
|
||||
// ReservedKey is the key of module's reservation of series (MAJOR.MINOR).
|
||||
@@ -96,7 +108,7 @@ func SplitVersion(version string) (series string, patch int, err error) {
|
||||
// Reserve reserves the next version of module's series for a build that starts, for artifacts (their names), and answers
|
||||
// it. The next patch is the one after the higher of the highest released and the one reserved, 0 when there is neither. A
|
||||
// conflicting write (another build reserved first) is read again and the next taken.
|
||||
func (r *Reserver) Reserve(ctx context.Context, module, series string, artifacts []string) (ReservedVersion, error) {
|
||||
func (r *Reserver) Reserve(ctx context.Context, module, series string, artifacts []string, src Source) (ReservedVersion, error) {
|
||||
if r.Filled != nil && !r.Filled() {
|
||||
return ReservedVersion{}, ErrNotFilled
|
||||
}
|
||||
@@ -122,7 +134,11 @@ func (r *Reserver) Reserve(ctx context.Context, module, series string, artifacts
|
||||
next = p
|
||||
}
|
||||
}
|
||||
out := ReservedVersion{V: 1, Module: module, Series: series, Version: fmt.Sprintf("%s.%d", series, next+1), Artifacts: map[string]string{}, At: r.now()}
|
||||
out := ReservedVersion{V: 1, Module: module, Series: series, Version: fmt.Sprintf("%s.%d", series, next+1), Artifacts: map[string]string{}, At: r.now(),
|
||||
Repository: src.Repository, Commit: src.Commit}
|
||||
if src.GoPackage {
|
||||
out.Tag = GoTagOf(out.Version)
|
||||
}
|
||||
for _, a := range artifacts {
|
||||
out.Artifacts[a] = ""
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestReservation(t *testing.T) {
|
||||
arts := []string{"m:a", "m:b"}
|
||||
|
||||
filled = false
|
||||
if _, err := r.Reserve(ctx, "m", "0.1", arts); !errors.Is(err, ErrNotFilled) {
|
||||
if _, err := r.Reserve(ctx, "m", "0.1", arts, Source{}); !errors.Is(err, ErrNotFilled) {
|
||||
t.Fatalf("before versions-filled: %v, want ErrNotFilled", err)
|
||||
}
|
||||
if got, _ := r.All(ctx); len(got) != 0 {
|
||||
@@ -54,25 +54,25 @@ func TestReservation(t *testing.T) {
|
||||
}
|
||||
filled = true
|
||||
|
||||
first, err := r.Reserve(ctx, "m", "0.1", arts)
|
||||
first, err := r.Reserve(ctx, "m", "0.1", arts, Source{})
|
||||
if err != nil || first.Version != "0.1.20" {
|
||||
t.Fatalf("first build reserves %q (%v), want 0.1.20 after the highest released 0.1.19", first.Version, err)
|
||||
}
|
||||
second, err := r.Reserve(ctx, "m", "0.1", arts)
|
||||
second, err := r.Reserve(ctx, "m", "0.1", arts, Source{})
|
||||
if err != nil || second.Version != "0.1.21" {
|
||||
t.Fatalf("second build reserves %q (%v), want 0.1.21: never the first's", second.Version, err)
|
||||
}
|
||||
// Two builds at once: the rival reserves between this one's read and write, and this one takes the next.
|
||||
store.onUpdate = func() {
|
||||
if _, err := r.Reserve(ctx, "m", "0.1", arts); err != nil {
|
||||
if _, err := r.Reserve(ctx, "m", "0.1", arts, Source{}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
third, err := r.Reserve(ctx, "m", "0.1", arts)
|
||||
third, err := r.Reserve(ctx, "m", "0.1", arts, Source{})
|
||||
if err != nil || third.Version != "0.1.23" {
|
||||
t.Fatalf("a build that lost the compare-and-set reserves %q (%v), want 0.1.23", third.Version, err)
|
||||
}
|
||||
if v, _ := r.Reserve(ctx, "m", "0.3", arts); v.Version != "0.3.0" {
|
||||
if v, _ := r.Reserve(ctx, "m", "0.3", arts, Source{}); v.Version != "0.3.0" {
|
||||
t.Errorf("a series with nothing released reserves %q, want 0.3.0", v.Version)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestReservation(t *testing.T) {
|
||||
if all, _ := r.All(ctx); len(all) != 0 {
|
||||
t.Errorf("reservations left: %v", all)
|
||||
}
|
||||
if again, _ := r.Reserve(ctx, "m", "0.1", arts); again.Version != "0.1.20" {
|
||||
if again, _ := r.Reserve(ctx, "m", "0.1", arts, Source{}); again.Version != "0.1.20" {
|
||||
t.Logf("with the catalog list unchanged the next is %s", again.Version)
|
||||
}
|
||||
if ok, _ := r.Drop(ctx, "m", "0.1"); !ok {
|
||||
@@ -118,7 +118,7 @@ func TestReservationIsReadAndMarkedPublished(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := &reserveMem{mem: newMem()}
|
||||
r := &Reserver{Store: store, ReleasedOf: func(context.Context, string) ([]string, error) { return []string{"1.4.2"}, nil }}
|
||||
v, err := r.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"})
|
||||
v, err := r.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, Source{})
|
||||
if err != nil || v.Version != "1.4.3" {
|
||||
t.Fatalf("reserved %q (%v), want 1.4.3", v.Version, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package versions
|
||||
|
||||
// GoTagOf is the git tag a Go package's released version is written as (design 47 G §370, design 51 *The versions*).
|
||||
func GoTagOf(version string) string { return "go/v" + version }
|
||||
@@ -132,6 +132,7 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
|
||||
// The watchdogs seat's presence says a node's machine is heard (issue 1966, ADR 0596 §2, ADR 0668 §3).
|
||||
if want := []string{"mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-forge-bridge.bridge_tags", "mesh-delivery-indexer.indexer_read_candidates", "mesh-delivery-indexer.indexer_read_manifests",
|
||||
"mesh-delivery-indexer.indexer_read_modules", "mesh-delivery-indexer.indexer_read_packages", "mesh-delivery-indexer.indexer_requirements", "seat:container-image-registry.remove", "seat:container-image-registry.versions",
|
||||
"seat:git.tag",
|
||||
"seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held", "seat:mesh-catalog.versions", "seat:mesh-hand-acts.hand-act", "seat:mesh-watchdogs.presence", "seat:node-build-go.drop", "seat:node-build-image.drop", "seat:node-build-node.drop", "seat:node-engine.manifest-reads", "seat:npm-package-registry.remove",
|
||||
"seat:npm-package-registry.versions", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions"}; !reflect.DeepEqual(sorted(m.Invokes), want) || len(m.Config) != 0 {
|
||||
t.Errorf("invokes = %v, config = %v, want %v and no config", m.Invokes, m.Config, want)
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
"mesh-delivery-indexer.indexer_requirements",
|
||||
"seat:container-image-registry.remove",
|
||||
"seat:container-image-registry.versions",
|
||||
"seat:git.tag",
|
||||
"seat:go-package-registry.remove",
|
||||
"seat:go-package-registry.versions",
|
||||
"seat:mesh-artifact-sweep.held",
|
||||
|
||||
Reference in New Issue
Block a user