diff --git a/cmd/mesh-controller/build.go b/cmd/mesh-controller/build.go index 2f360a66..10b20eba 100644 --- a/cmd/mesh-controller/build.go +++ b/cmd/mesh-controller/build.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "github.com/novox/mesh-controller/internal/conditions" + "io" "os" "slices" "strings" @@ -236,47 +237,56 @@ func buildsCommand(ctx context.Context, args []string) error { } for _, b := range builds { - what := b.Module - if what == "" { - // It failed before knowing what it was building, which is most of the interesting - // failures. The repository is what a person has to go and look at. - what = "?" - } - outcome := "built " + short(b.Commit) - if !b.Worked() { - outcome = "failed" - } - fmt.Printf("%-18s %-14s %-10s %s %s\n", - what, outcome, b.On, b.At.Local().Format("2006-01-02 15:04"), b.ID) - fmt.Printf(" %s", b.Repository) - if b.Ref != "" { - fmt.Printf(" at %s", b.Ref) - } - fmt.Println() - for _, made := range b.Made { - fmt.Printf(" %-10s %s\n", made.Kind, made.Reference) - } - // What it published and under what reuse key (novox/hq ADR 0408 §1). - if p := b.Published; p != nil { - if p.Artifact != "" { - fmt.Printf(" published %s\n", p.Artifact) - } - if p.Package != nil { - fmt.Printf(" published %s@%s\n", p.Package.Name, p.Package.Version) - } - } - if b.ReuseKey != "" { - fmt.Printf(" reuse key %s\n", b.ReuseKey) - } - if !b.Worked() { - // The builder's own first line. The whole failure is often a build log, and printing - // it here would bury every other row. - fmt.Printf(" %s\n", firstLine(b.Failed)) - } + printBuild(os.Stdout, b) } return nil } +// printBuild says one build: what, its outcome, where and when, what it made and published, its reuse key, and its +// source fingerprint (novox/hq issue 1204: the one tool that reads what the builder recorded it was made from). +func printBuild(w io.Writer, b inventory.Build) { + what := b.Module + if what == "" { + // It failed before knowing what it was building, which is most of the interesting + // failures. The repository is what a person has to go and look at. + what = "?" + } + outcome := "built " + short(b.Commit) + if !b.Worked() { + outcome = "failed" + } + fmt.Fprintf(w, "%-18s %-14s %-10s %s %s\n", + what, outcome, b.On, b.At.Local().Format("2006-01-02 15:04"), b.ID) + fmt.Fprintf(w, " %s", b.Repository) + if b.Ref != "" { + fmt.Fprintf(w, " at %s", b.Ref) + } + fmt.Fprintln(w) + for _, made := range b.Made { + fmt.Fprintf(w, " %-10s %s\n", made.Kind, made.Reference) + } + // What it published and under what reuse key (novox/hq ADR 0408 §1). + if p := b.Published; p != nil { + if p.Artifact != "" { + fmt.Fprintf(w, " published %s\n", p.Artifact) + } + if p.Package != nil { + fmt.Fprintf(w, " published %s@%s\n", p.Package.Name, p.Package.Version) + } + } + if b.ReuseKey != "" { + fmt.Fprintf(w, " reuse key %s\n", b.ReuseKey) + } + if b.SourceFingerprint != "" { + fmt.Fprintf(w, " source %s\n", b.SourceFingerprint) + } + if !b.Worked() { + // The builder's own first line. The whole failure is often a build log, and printing + // it here would bury every other row. + fmt.Fprintf(w, " %s\n", firstLine(b.Failed)) + } +} + // builderCommand issues a build machine its own broker credential. // // **A build machine is not a node**, and giving it a node's account would let it read another diff --git a/cmd/mesh-controller/build_test.go b/cmd/mesh-controller/build_test.go index f7fb0490..ab1bf2e7 100644 --- a/cmd/mesh-controller/build_test.go +++ b/cmd/mesh-controller/build_test.go @@ -1,9 +1,12 @@ package main import ( + "bytes" + "strings" "testing" "time" + "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) @@ -32,3 +35,34 @@ func TestABuildIDSaysWhenItWasAsked(t *testing.T) { } } } + +// novox/hq issue 1204: builds says the source fingerprint the builder recorded, the one tool that reads it. +func TestBuildsSaysTheSourceFingerprint(t *testing.T) { + at := time.Date(2026, 10, 19, 10, 40, 0, 0, time.UTC) + built := inventory.Build{ID: "build-1", Module: "mailu", Commit: "b4527d9091e972f28cb365623d9280045e7741a3", On: "ace", + Repository: "novox/mesh-catalog", At: at, ReuseKey: "r1:abc", + SourceFingerprint: "src1:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"} + + // 1204/AC1 + var out bytes.Buffer + printBuild(&out, built) + lines := strings.Split(strings.TrimRight(out.String(), "\n"), "\n") + if last := lines[len(lines)-1]; last != " source "+built.SourceFingerprint { + t.Errorf("last line %q; want the source line after the reuse key:\n%s", last, out.String()) + } + if !strings.Contains(lines[len(lines)-2], "reuse key r1:abc") { + t.Errorf("the source line does not follow the reuse key:\n%s", out.String()) + } + + // 1204/AC2 + without := built + without.SourceFingerprint = "" + var plain bytes.Buffer + printBuild(&plain, without) + if strings.Contains(plain.String(), "source") { + t.Errorf("a build with no fingerprint says a source line:\n%s", plain.String()) + } + if want := strings.Join(lines[:len(lines)-1], "\n") + "\n"; plain.String() != want { + t.Errorf("the other lines changed:\n%s\nwant\n%s", plain.String(), want) + } +}