Self-upgrade installer: SDK-by-version, mesh-controller rename, foundation adopted #26

Merged
jschoubben merged 17 commits from feat/a-bed-that-hands-over-nothing into main 2026-09-16 21:21:34 +00:00
4 changed files with 57 additions and 13 deletions
Showing only changes of commit 2fb700d61b - Show all commits
+9 -9
View File
@@ -139,7 +139,7 @@ func run() error {
return err
}
fmt.Printf("building for the mesh, publishing to %s\n", registry)
fmt.Fprintf(os.Stderr, "building for the mesh, publishing to %s\n", registry)
publisher := builder.Registry{Address: registry, Run: builder.Command}
for {
@@ -164,14 +164,14 @@ func answer(ctx context.Context, channel *amqp.Channel, publisher builder.Publis
// until it either finishes or fails is indistinguishable from one that never arrived — which
// cost a long diagnosis against a running mesh, chasing "the handler never fired" when the
// truth was only that the handler said nothing until the end.
fmt.Printf("a build request arrived (%d bytes)\n", len(delivery.Body))
fmt.Fprintf(os.Stderr, "a build request arrived (%d bytes)\n", len(delivery.Body))
var request link.BuildRequest
if err := json.Unmarshal(delivery.Body, &request); err != nil {
// Unreadable. Acknowledged and dropped rather than requeued: a message this builder
// cannot parse will not become parseable by being delivered again, and requeueing it
// would put it in front of every real request for ever.
fmt.Printf("a request could not be read and was dropped: %v\n", err)
fmt.Fprintf(os.Stderr, "a request could not be read and was dropped: %v\n", err)
_ = delivery.Ack(false)
return
}
@@ -180,19 +180,19 @@ func answer(ctx context.Context, channel *amqp.Channel, publisher builder.Publis
ID: request.ID, Repository: request.Repository, Path: request.Path,
Ref: request.Ref, On: on,
}
fmt.Printf("building %s", request.Repository)
fmt.Fprintf(os.Stderr, "building %s", request.Repository)
if request.Path != "" {
fmt.Printf(" at %s", request.Path)
fmt.Fprintf(os.Stderr, " at %s", request.Path)
}
if request.Ref != "" {
fmt.Printf(" at %s", request.Ref)
fmt.Fprintf(os.Stderr, " at %s", request.Ref)
}
fmt.Println()
fmt.Fprintln(os.Stderr)
built, err := builder.Build(ctx, builder.Command, publisher,
request.Repository, request.Path, request.Ref, workspace, request.Held,
func(step, message string) {
fmt.Printf(" [%s] %s\n", step, message)
fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message)
})
if err != nil {
// A failure is a result. A build that fails and says nothing is indistinguishable from a
@@ -212,7 +212,7 @@ func answer(ctx context.Context, channel *amqp.Channel, publisher builder.Publis
})
}
result.Against = built.Against
fmt.Printf(" built %s from %s\n", built.Manifest.Module, short(built.Commit))
fmt.Fprintf(os.Stderr, " built %s from %s\n", built.Manifest.Module, short(built.Commit))
}
}
+1 -1
View File
@@ -85,7 +85,7 @@ func buildOnce(ctx context.Context, args []string) error {
fmt.Fprintln(os.Stderr)
built, buildErr := builder.Build(ctx, builder.Command, publisher, repository, *path, *ref, where, bases,
func(step, message string) { fmt.Printf(" [%s] %s\n", step, message) })
func(step, message string) { fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message) })
if buildErr != nil {
return buildErr
}
+44
View File
@@ -0,0 +1,44 @@
package main
import (
"os"
"strings"
"testing"
)
// **The genesis path writes its result to stdout and nothing else there.** The installer captures
// stdout alone and parses it as JSON; one stray log line makes that fail with "invalid character"
// — exactly what a builder fmt.Printf caused, breaking a build that had worked. This keeps
// diagnostics on stderr so a future print cannot repeat it silently.
func TestBuilderDiagnosticsStayOffStdout(t *testing.T) {
allowed := map[string]bool{
"string(body)": true, // once.go: the result JSON, which IS stdout
"version)": true, // --version
`"stopping")`: true, // the loop.s shutdown line
"usage)": true, // --help text, for a human
}
for _, file := range []string{"once.go", "main.go"} {
src, err := os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
for i, raw := range strings.Split(string(src), "\n") {
line := strings.TrimSpace(raw)
if !strings.HasPrefix(line, "fmt.Printf(") &&
!strings.HasPrefix(line, "fmt.Println(") &&
!strings.HasPrefix(line, "fmt.Print(") {
continue
}
ok := false
for token := range allowed {
if strings.Contains(line, token) {
ok = true
}
}
if !ok {
t.Errorf("%s:%d writes a diagnostic to stdout, which the installer parses as JSON:\n %s",
file, i+1, line)
}
}
}
}
+3 -3
View File
@@ -478,16 +478,16 @@ func Command(ctx context.Context, dir, name string, args ...string) (string, err
// nothing" and "git clone is waiting on a network that will not answer". Silent on success is
// what made an empty workspace unreadable.
started := timeNow()
fmt.Printf(" $ (%s) %s %s\n", short(filepath.Base(dir)), name, strings.Join(args, " "))
fmt.Fprintf(os.Stderr, " $ (%s) %s %s\n", short(filepath.Base(dir)), name, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, name, args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf(" ! %s %s failed after %s\n", name, args[0], since(started))
fmt.Fprintf(os.Stderr, " ! %s %s failed after %s\n", name, args[0], since(started))
return string(out), fmt.Errorf("%s %s: %w\n%s",
name, strings.Join(args, " "), err, strings.TrimSpace(string(out)))
}
fmt.Printf(" ✓ %s %s (%s)\n", name, firstArg(args), since(started))
fmt.Fprintf(os.Stderr, " ✓ %s %s (%s)\n", name, firstArg(args), since(started))
return string(out), nil
}