diff --git a/go.mod b/go.mod index 6323d33..aeb417a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( ) require ( - gitea.hostxtra.co.uk/vantage/vantage-shared v0.2.1 + gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0 golang.org/x/net v0.58.0 // indirect golang.org/x/text v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260908043556-f8649ddbbfe6 // indirect diff --git a/go.sum b/go.sum index a9f434a..665a4e2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -gitea.hostxtra.co.uk/vantage/vantage-shared v0.2.1 h1:rPzXSRwU+4+F2pdkmDrIxKsIzqz3S6feJEWalGmKqfU= -gitea.hostxtra.co.uk/vantage/vantage-shared v0.2.1/go.mod h1:dWjeOFLltQ8sv9Pnn1xRxGfWGgqa2fkG0esuaJLoPXQ= +gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0 h1:xwSIEkQKTd4Qk+BYHvoGN+h84Isr2h5qqnitUWF1m2w= +gitea.hostxtra.co.uk/vantage/vantage-shared v0.5.0/go.mod h1:Zo66XhqF8No3dveIowLCepvMxVg8KnhsNMz0k0Xpuck= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= diff --git a/internal/updates/tailbuf.go b/internal/updates/tailbuf.go new file mode 100644 index 0000000..0fad275 --- /dev/null +++ b/internal/updates/tailbuf.go @@ -0,0 +1,34 @@ +package updates + +import "sync" + +// outputTailMax bounds the package-manager output carried back in a +// PatchResult. The end of the output is where apt and dnf say what failed, so +// the newest bytes are the ones kept. +const outputTailMax = 64 << 10 + +// tailBuffer is an io.Writer that retains only the last max bytes written. +// Stdout and stderr are both pointed at one, so it is safe for concurrent use. +type tailBuffer struct { + mu sync.Mutex + max int + buf []byte +} + +func newTailBuffer(max int) *tailBuffer { return &tailBuffer{max: max} } + +func (t *tailBuffer) Write(p []byte) (int, error) { + t.mu.Lock() + defer t.mu.Unlock() + t.buf = append(t.buf, p...) + if over := len(t.buf) - t.max; over > 0 { + t.buf = append([]byte(nil), t.buf[over:]...) + } + return len(p), nil +} + +func (t *tailBuffer) String() string { + t.mu.Lock() + defer t.mu.Unlock() + return string(t.buf) +} diff --git a/internal/updates/tailbuf_test.go b/internal/updates/tailbuf_test.go new file mode 100644 index 0000000..a354281 --- /dev/null +++ b/internal/updates/tailbuf_test.go @@ -0,0 +1,34 @@ +package updates + +import ( + "strings" + "testing" +) + +func TestTailBufferKeepsNewestBytes(t *testing.T) { + b := newTailBuffer(10) + _, _ = b.Write([]byte("0123456789")) + _, _ = b.Write([]byte("abcde")) + if got := b.String(); got != "56789abcde" { + t.Fatalf("got %q, want %q", got, "56789abcde") + } +} + +func TestTailBufferSingleWriteLargerThanMax(t *testing.T) { + b := newTailBuffer(4) + n, err := b.Write([]byte("abcdefgh")) + if err != nil || n != 8 { + t.Fatalf("Write must report the full length consumed, got %d, %v", n, err) + } + if got := b.String(); got != "efgh" { + t.Fatalf("got %q", got) + } +} + +func TestTailBufferDefaultCap(t *testing.T) { + b := newTailBuffer(outputTailMax) + _, _ = b.Write([]byte(strings.Repeat("x", outputTailMax+100))) + if len(b.String()) != outputTailMax { + t.Fatalf("len %d, want %d", len(b.String()), outputTailMax) + } +}