From 8b2e8895c223a89b274372c07408c2912ce2e75b Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 18:03:39 +0200 Subject: [PATCH] Prove on a real bus that every done key names its references, a refused push is built again, and a pinned upstream is copied (issue 1587, B8, ADR 0498 checks) --- .../mesh-delivery-builder/integration_test.go | 53 +++++++++++- .../catalogue-49caf17f-hello-web.module.json | 82 +++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 modules/mesh-delivery-builder/cmd/mesh-delivery-builder/testdata/catalogue-49caf17f-hello-web.module.json diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go index 6dd6b42..3dcd257 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go @@ -54,6 +54,8 @@ type fakeBuildModules struct { ctx context.Context n atomic.Int64 failBuild atomic.Bool + // refusePush makes every bundle and archive build fail as a push the artifact store refused (ADR 0498 clause 2). + refusePush atomic.Bool mu sync.Mutex calls map[string]int // by `.` fetches []map[string]any @@ -121,6 +123,14 @@ func (f *fakeBuildModules) serve(t *testing.T, nc *nats.Conn, module string) { step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"workspace":"w`+id+`","commit":"`+commit+`"`+merge+`}`) case f.failBuild.Load() && node == "n1": step.State, step.Reason = delivery.StepFailed, fmt.Sprintf("go build of %v: boom", args["artifact"]) + case tool == "build_image_copy": // the upstream copy answers the digest and the registry's reference (ADR 0498 clause 4) + d := digestOf(fmt.Sprint(args["artifact"])) + step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"artifact":"`+fmt.Sprint(args["artifact"])+`","digest":"`+d+`","reference":"registry.example/`+fmt.Sprint(args["module"])+`/`+fmt.Sprint(args["artifact"])+`@`+d+`","pushed":true}`) + case tool != "build_image_build" && f.refusePush.Load(): + step.State, step.Reason = delivery.StepFailed, fmt.Sprintf("%s%v: the artifact store answered 507 Insufficient Storage", delivery.NotStored, args["artifact"]) + case tool != "build_image_build": // a bundle or an archive is pushed and its reference answered (ADR 0498 clause 1) + d := digestOf(fmt.Sprint(args["artifact"])) + step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"digest":"`+d+`","reference":"artifact-store://`+filepath.Base(fmt.Sprint(args["module"]))+`/`+fmt.Sprint(args["artifact"])+`/blobs/`+d+`"}`) case tool == "build_image_build" && args["publish"] == true: // a trunk image is pushed: the digest and the reference d := digestOf(fmt.Sprint(args["artifact"])) step.State, step.Result = delivery.StepPassed, json.RawMessage(`{"digest":"`+d+`","reference":"registry.example/imaged/server@`+d+`"}`) @@ -374,8 +384,8 @@ func TestBuilderMakesADoneKeyWithTheDigests(t *testing.T) { // 1607/AC7 t.Fatalf("build = %+v", v) } for _, a := range v.Artifacts { - if a.Digest != digestOf(a.Name) || a.Kind != "bundle" || a.Reference != "" { - t.Errorf("artifact = %+v, want the digest every node built and no reference", a) + if a.Digest != digestOf(a.Name) || a.Kind != "bundle" || a.Reference != "artifact-store://netcheck/"+a.Name+"/blobs/"+a.Digest { + t.Errorf("artifact = %+v, want the digest every node built and the reference its push answered (ADR 0498 clause 1)", a) } } if v.Toolchains["go"].Version != "go1.27.1" || v.Toolchains["typescript"].Version != "tsc5" { @@ -674,3 +684,42 @@ func TestAnImageIsBuiltOnOneNodeAndOnlyATrunkKeyPublishes(t *testing.T) { // 170 t.Errorf("builds = %v, want three, two of them with publish (the trunk's two fingerprints)", b.fake.builds) } } + +// TestEveryDoneKeyNamesItsReferencesAndARefusedPushIsBuiltAgain: with the artifact store refusing pushes, the key of an +// archive module ends failed opening "not stored:"; once it takes them, a new modules value builds the key again, done, its +// every artifact naming a reference (issue 1587, B8, ADR 0498 clauses 1, 2 and 8). +func TestEveryDoneKeyNamesItsReferencesAndARefusedPushIsBuiltAgain(t *testing.T) { + b := startBed(t) + b.fake.refusePush.Store(true) + b.archive(t, "ca-trust", "modules/ca-trust", commit) + b.start(t, "n1", nil) + v := b.await(t, "ca-trust", isState(delivery.BuildFailed)) + if !strings.HasPrefix(v.Error, delivery.NotStored) || !strings.Contains(v.Error, "507") { + t.Fatalf("build = %+v, want failed opening %q with the store's words", v, delivery.NotStored) + } + b.fake.refusePush.Store(false) + b.archive(t, "ca-trust", "modules/ca-trust", "89abcdef0123456789abcdef0123456789abcdef") + v = b.await(t, "ca-trust", isState(delivery.BuildDone)) + if ok, missing := v.Referenced(); !ok { + t.Errorf("build = %+v, artifacts naming no reference: %v", v, missing) + } +} + +// TestAPinnedUpstreamIsCopiedByTheImageBuildModule: hello-web's manifest (captured from mesh-catalog 49caf17f) names an +// upstream image pinned by its digest and an archive; the upstream is copied on n1, where mesh-build-image runs, with no +// fetch of the image module, and the key ends done with both references (issue 1587, B8, ADR 0498 clause 4). +func TestAPinnedUpstreamIsCopiedByTheImageBuildModule(t *testing.T) { + b := startBed(t) + b.moduleCommit(t, "hello-web", "catalogue-49caf17f-hello-web.module.json", "modules/hello-web", nil, commit) + b.start(t, "n1", nil) + v := b.await(t, "hello-web", isState(delivery.BuildDone)) + if ok, missing := v.Referenced(); !ok { + t.Fatalf("build = %+v, artifacts naming no reference: %v", v, missing) + } + if n := b.fake.count("mesh-build-image.build_image_copy"); n != 1 { + t.Errorf("build_image_copy called %d times, want once", n) + } + if n := b.fake.count("mesh-build-image.build_image_fetch"); n != 0 { + t.Errorf("build_image_fetch called %d times, want none for a copy", n) + } +} diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/testdata/catalogue-49caf17f-hello-web.module.json b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/testdata/catalogue-49caf17f-hello-web.module.json new file mode 100644 index 0000000..005578c --- /dev/null +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/testdata/catalogue-49caf17f-hello-web.module.json @@ -0,0 +1,82 @@ +{ + "module": "hello-web", + "slug": "hello", + "version": "1", + "capabilities": [ + "container-runtime" + ], + "requires": [ + "route" + ], + "contributes": { + "route": { + "label": "hello", + "endpoint": "web" + } + }, + "binds": { + "route": "${dir:state}/route.json" + }, + "listens": [ + { + "name": "web", + "port": 8080, + "protocol": "tcp", + "from": "mesh", + "why": "the demo web page; only the route-proxy reaches it, and the public name is a route grant" + } + ], + "resources": [ + { + "id": "state", + "type": "directory", + "mode": "0700", + "place": ".", + "unknown": "program" + }, + { + "id": "page", + "type": "file", + "path": "${dir:state}/index.html", + "mode": "0644", + "template": "files/index.html" + }, + { + "id": "net", + "type": "network", + "name": "hello-web" + }, + { + "id": "server", + "type": "container", + "name": "hello-web", + "network": "hello-web", + "ports": [ + "8080" + ], + "volumes": [ + "${dir:state}/index.html:/www/index.html:ro" + ], + "args": [ + "sh", + "-c", + "while true; do { printf 'HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nConnection: close\\r\\n\\r\\n'; cat /www/index.html; } | nc -l -p 8080; done" + ], + "artifact": "server" + } + ], + "build": { + "artifacts": [ + { + "name": "server", + "kind": "upstream", + "from": "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b" + }, + { + "name": "files", + "kind": "archive", + "from": "files" + } + ] + } +}