Write a failed publish of a merged image to the key; read mesh-build-image's captured steps (review r2)
mesh/repo-check head db62e82e26d7 merged into trunk 47ab8cd3cf45 beside mesh-catalog#435@7c5d11e5 — pass: its merge-check.sh passed
mesh/merge-gate head db62e82e26d7 merged into trunk 47ab8cd3cf45 — pass: group feat/r4c9-image-build composed: every machine composes with the change as it…
mesh/check pass db62e82e26d7 on 2b635e2138ae | unit 366 run 0 kept | int 405 run 0 kept | clone 9.2s unit 191ms int 6.7s+44s | composition not yet run
mesh/delivery delivered
mesh/delivery-group group feat/r4c9-image-build delivered: every member is delivered
mesh/repo-check head db62e82e26d7 merged into trunk 47ab8cd3cf45 beside mesh-catalog#435@7c5d11e5 — pass: its merge-check.sh passed
mesh/merge-gate head db62e82e26d7 merged into trunk 47ab8cd3cf45 — pass: group feat/r4c9-image-build composed: every machine composes with the change as it…
mesh/check pass db62e82e26d7 on 2b635e2138ae | unit 366 run 0 kept | int 405 run 0 kept | clone 9.2s unit 191ms int 6.7s+44s | composition not yet run
mesh/delivery delivered
mesh/delivery-group group feat/r4c9-image-build delivered: every member is delivered
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// result reads a captured step value of mesh-build-image (testdata/captured/README) and returns its result.
|
||||||
|
func captured(t *testing.T, name string) json.RawMessage {
|
||||||
|
t.Helper()
|
||||||
|
raw, err := os.ReadFile("testdata/captured/" + name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var step delivery.Step
|
||||||
|
if err := json.Unmarshal(raw, &step); err != nil {
|
||||||
|
t.Fatalf("%s: %v", name, err)
|
||||||
|
}
|
||||||
|
if step.State != delivery.StepPassed || len(step.Result) == 0 {
|
||||||
|
t.Fatalf("%s: state %q, result %s, want a passed step with a result", name, step.State, step.Result)
|
||||||
|
}
|
||||||
|
return step.Result
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCapturedImageStepsDecodeWithTheBuildersOwnTypes(t *testing.T) { // review r2, mesh-catalog 2; ADR 0487 clause 1
|
||||||
|
var f fetchResult
|
||||||
|
if err := json.Unmarshal(captured(t, "fetch.json"), &f); err != nil || f.Workspace == "" {
|
||||||
|
t.Errorf("fetch = %+v, %v, want a workspace (the field the builder reads)", f, err)
|
||||||
|
}
|
||||||
|
var trunk buildResult
|
||||||
|
if err := json.Unmarshal(captured(t, "build-trunk.json"), &trunk); err != nil || trunk.Digest == "" || trunk.Reference == "" {
|
||||||
|
t.Errorf("trunk build = %+v, %v, want a digest and a reference", trunk, err)
|
||||||
|
}
|
||||||
|
var cand buildResult
|
||||||
|
if err := json.Unmarshal(captured(t, "build-candidate.json"), &cand); err != nil || cand.Digest == "" || cand.Reference != "" {
|
||||||
|
t.Errorf("candidate build = %+v, %v, want a digest and no reference", cand, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -310,6 +310,7 @@ func (b *Builder) try(ctx context.Context, module string, m delivery.Module) {
|
|||||||
// built as a candidate's; the other artifacts stay as the done key has them (ADR 0487 clauses 5 and 8).
|
// built as a candidate's; the other artifacts stay as the done key has them (ADR 0487 clauses 5 and 8).
|
||||||
var prior *delivery.BuilderBuild
|
var prior *delivery.BuilderBuild
|
||||||
var only map[string]bool
|
var only map[string]bool
|
||||||
|
publishing := false
|
||||||
if cl.Build.State == delivery.BuildDone {
|
if cl.Build.State == delivery.BuildDone {
|
||||||
prior, only = &cl.Build, map[string]bool{}
|
prior, only = &cl.Build, map[string]bool{}
|
||||||
for _, bm := range OneNodeModules(cl.Build) {
|
for _, bm := range OneNodeModules(cl.Build) {
|
||||||
@@ -317,6 +318,7 @@ func (b *Builder) try(ctx context.Context, module string, m delivery.Module) {
|
|||||||
}
|
}
|
||||||
if trunk && Unpublished(cl.Build) {
|
if trunk && Unpublished(cl.Build) {
|
||||||
only[ImageBuildModule] = true
|
only[ImageBuildModule] = true
|
||||||
|
publishing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result := b.build(ctx, module, m, plan, planErr, prior, only)
|
result := b.build(ctx, module, m, plan, planErr, prior, only)
|
||||||
@@ -328,7 +330,11 @@ func (b *Builder) try(ctx context.Context, module string, m delivery.Module) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
result.By, result.Manifest = b.Claimer.By, m.Manifest
|
result.By, result.Manifest = b.Claimer.By, m.Manifest
|
||||||
if cl.Build.State == delivery.BuildDone && result.State == delivery.BuildFailed && !strings.HasPrefix(result.Error, buildrun.DigestsDifferPrefix) {
|
if publishing && result.State == delivery.BuildFailed {
|
||||||
|
// The trunk's image was built only as a candidate's and its push failed: the key says so, so the builds view, the
|
||||||
|
// checker and the failed-key retake see it, and a look does not build the image again for nothing.
|
||||||
|
result.Error = "publishing the image of the merged commit failed: " + result.Error
|
||||||
|
} else if cl.Build.State == delivery.BuildDone && result.State == delivery.BuildFailed && !strings.HasPrefix(result.Error, buildrun.DigestsDifferPrefix) {
|
||||||
// A done key built again on a second node keeps its done value when the new build fails for another reason than
|
// A done key built again on a second node keeps its done value when the new build fails for another reason than
|
||||||
// the digests: the first build stands, and a later change of the nodes looks again.
|
// the digests: the first build stands, and a later change of the nodes looks again.
|
||||||
b.logf("%s: built again on a second node and failed, the done build stands: %s", cl.Key, result.Error)
|
b.logf("%s: built again on a second node and failed, the done build stands: %s", cl.Key, result.Error)
|
||||||
@@ -597,6 +603,18 @@ func (b *Builder) step(ctx context.Context, bm, node, tool string, args map[stri
|
|||||||
return b.Runner.Run(ctx, bm, node, toolPrefix[bm]+tool, args)
|
return b.Runner.Run(ctx, bm, node, toolPrefix[bm]+tool, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchResult and buildResult are the results of a build module's fetch and build steps as the builder reads them.
|
||||||
|
type fetchResult struct {
|
||||||
|
Workspace string `json:"workspace"`
|
||||||
|
Merge string `json:"merge"`
|
||||||
|
Conflicts []string `json:"conflicts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildResult struct {
|
||||||
|
Digest string `json:"digest"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
}
|
||||||
|
|
||||||
// runLane takes a place on the node, fetches the commit there and builds each of the lane's artifacts, one step at a time.
|
// runLane takes a place on the node, fetches the commit there and builds each of the lane's artifacts, one step at a time.
|
||||||
func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m delivery.Module, module string) {
|
func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m delivery.Module, module string) {
|
||||||
release, err := b.Places.Acquire(ctx, l.node)
|
release, err := b.Places.Acquire(ctx, l.node)
|
||||||
@@ -619,11 +637,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m del
|
|||||||
l.err = err
|
l.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var fetched struct {
|
var fetched fetchResult
|
||||||
Workspace string `json:"workspace"`
|
|
||||||
Merge string `json:"merge"`
|
|
||||||
Conflicts []string `json:"conflicts"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(raw, &fetched); err != nil || fetched.Workspace == "" {
|
if err := json.Unmarshal(raw, &fetched); err != nil || fetched.Workspace == "" {
|
||||||
l.err = fmt.Errorf("the fetch on %s answered no workspace: %s", l.node, raw)
|
l.err = fmt.Errorf("the fetch on %s answered no workspace: %s", l.node, raw)
|
||||||
return
|
return
|
||||||
@@ -652,10 +666,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m del
|
|||||||
l.err = err
|
l.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var built struct {
|
var built buildResult
|
||||||
Digest string `json:"digest"`
|
|
||||||
Reference string `json:"reference"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(raw, &built); err != nil || built.Digest == "" {
|
if err := json.Unmarshal(raw, &built); err != nil || built.Digest == "" {
|
||||||
l.err = fmt.Errorf("the build of %s of %s on %s answered no digest: %s", t.Artifact, module, l.node, raw)
|
l.err = fmt.Errorf("the build of %s of %s on %s answered no digest: %s", t.Artifact, module, l.node, raw)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -606,3 +606,28 @@ func TestASecondGoNodeDoesNotBuildOrPushTheImageAgain(t *testing.T) { // review
|
|||||||
t.Errorf("%d go builds, want 3", n)
|
t.Errorf("%d go builds, want 3", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAFailedPublishOfACandidatesImageIsWrittenToTheKey(t *testing.T) { // review r2, mesh-delivery 1; ADR 0487 clause 5
|
||||||
|
r := newRig(t, imageArt)
|
||||||
|
r.b.Health = imageHave(t, "ace")
|
||||||
|
trunkModule(t, r)
|
||||||
|
r.b.Candidate(context.Background(), "feat%2Fx", candidateValue(t, "feat/x", 7, headX))
|
||||||
|
r.b.Wait()
|
||||||
|
if v := valueOf(t, r.st, r.key); v.State != delivery.BuildDone {
|
||||||
|
t.Fatalf("candidate build = %+v", v)
|
||||||
|
}
|
||||||
|
r.bm.fails["ace/build_image_build"] = "push to the registry: connection refused"
|
||||||
|
r.b.Module(context.Background(), "m", moduleValue(t, "modules/m")) // the merge: the trunk names the fingerprint
|
||||||
|
r.b.Wait()
|
||||||
|
v := valueOf(t, r.st, r.key)
|
||||||
|
if v.State != delivery.BuildFailed || !strings.Contains(v.Error, "connection refused") || !strings.Contains(v.Error, "publish") {
|
||||||
|
t.Fatalf("build = %+v, want failed naming the publish and its reason", v)
|
||||||
|
}
|
||||||
|
// The failed key is retaken as any failed key is, and ends published once the registry answers.
|
||||||
|
delete(r.bm.fails, "ace/build_image_build")
|
||||||
|
r.b.Module(context.Background(), "m", moduleValue(t, "modules/m"))
|
||||||
|
r.b.Wait()
|
||||||
|
if v := valueOf(t, r.st, r.key); v.State != delivery.BuildDone || v.Artifacts[0].Reference == "" {
|
||||||
|
t.Errorf("build = %+v, want done with a reference", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Captured step values of mesh-build-image (fetch, a candidate's build, a trunk's build), copied from
|
||||||
|
novox/mesh-catalog modules/mesh-build-image/cmd/mesh-build-image/testdata/captured/. Both copies must change together: the
|
||||||
|
module's capture test writes them, and the builder's TestCapturedImageStepsDecodeWithTheBuildersOwnTypes (internal/build,
|
||||||
|
fixtures_test.go) decodes these with the types the builder reads results with and fails on a missing field.
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"ended": "2026-10-22T08:00:01Z",
|
||||||
|
"id": "STEPID",
|
||||||
|
"node": "ace",
|
||||||
|
"result": {
|
||||||
|
"artifact": "server",
|
||||||
|
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||||
|
"pushed": false
|
||||||
|
},
|
||||||
|
"started": "2026-10-22T08:00:00Z",
|
||||||
|
"state": "passed",
|
||||||
|
"tool": "build_image_build",
|
||||||
|
"v": 1
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"ended": "2026-10-22T08:00:01Z",
|
||||||
|
"id": "STEPID",
|
||||||
|
"node": "ace",
|
||||||
|
"result": {
|
||||||
|
"artifact": "server",
|
||||||
|
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||||
|
"pushed": true,
|
||||||
|
"reference": "REGISTRY/fixture-image/server@sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
||||||
|
},
|
||||||
|
"started": "2026-10-22T08:00:00Z",
|
||||||
|
"state": "passed",
|
||||||
|
"tool": "build_image_build",
|
||||||
|
"v": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ended": "2026-10-22T08:00:01Z",
|
||||||
|
"id": "STEPID",
|
||||||
|
"node": "ace",
|
||||||
|
"result": {
|
||||||
|
"commit": "COMMIT",
|
||||||
|
"workspace": "WORKSPACE"
|
||||||
|
},
|
||||||
|
"started": "2026-10-22T08:00:00Z",
|
||||||
|
"state": "passed",
|
||||||
|
"tool": "build_image_fetch",
|
||||||
|
"v": 1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user