image/kernel pull: heartbeat dots so slow pulls look alive

Bundle downloads can take 20–60s on a typical connection and the
CLI was going silent between "resolving daemon" and the final image
summary. Users wondered whether banger had wedged.

New `withHeartbeat` helper wraps an RPC call with a dot-every-2s
ticker on stderr. No-op when stderr isn't a terminal, so piped or
scripted invocations stay quiet. Wired into `image pull` and `kernel
pull`, the two commands that actually download bytes.

Example:

    $ banger image pull debian-bookworm
    [image pull] ..........
    id  name             managed  ...

Tests cover the non-TTY short-circuit and error propagation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-18 17:08:30 -03:00
parent b5c13e3938
commit 2584f94828
No known key found for this signature in database
GPG key ID: 33112E6833C34679
2 changed files with 70 additions and 2 deletions

View file

@ -574,6 +574,33 @@ func TestVMRunProgressRendererSuppressesDuplicateLines(t *testing.T) {
}
}
func TestWithHeartbeatNoOpForNonTTY(t *testing.T) {
var buf bytes.Buffer
called := false
err := withHeartbeat(&buf, "image pull", func() error {
called = true
return nil
})
if err != nil {
t.Fatalf("withHeartbeat: %v", err)
}
if !called {
t.Fatal("fn should have been called")
}
if buf.Len() != 0 {
t.Fatalf("stderr = %q, want empty for non-TTY", buf.String())
}
}
func TestWithHeartbeatPropagatesError(t *testing.T) {
sentinel := errors.New("boom")
var buf bytes.Buffer
err := withHeartbeat(&buf, "image pull", func() error { return sentinel })
if !errors.Is(err, sentinel) {
t.Fatalf("withHeartbeat error = %v, want %v", err, sentinel)
}
}
func TestVMSetParamsFromFlagsConflict(t *testing.T) {
if _, err := vmSetParamsFromFlags("devbox", -1, -1, "", true, true); err == nil {
t.Fatal("expected nat conflict error")