From 0d8ca8eefe9497589af86e99cc534a74f87711ce Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 29 Jul 2025 13:28:46 -0700 Subject: [PATCH] Move pkg/jsonmessage to client/pkg/jsonmessage Signed-off-by: Derek McGowan Signed-off-by: Sebastiaan van Stijn --- client/go.mod | 4 +- client/go.sum | 7 + .../pkg}/jsonmessage/jsonmessage.go | 0 .../pkg}/jsonmessage/jsonmessage_test.go | 0 .../build/build_cgroupns_linux_test.go | 2 +- integration/build/build_test.go | 2 +- integration/build/build_userns_linux_test.go | 2 +- integration/container/copy_test.go | 2 +- integration/container/export_test.go | 2 +- integration/internal/build/build.go | 2 +- integration/internal/image/load.go | 2 +- integration/plugin/common/plugin_test.go | 2 +- integration/system/event_test.go | 2 +- testutil/fixtures/load/frozen.go | 2 +- .../client/pkg/jsonmessage/jsonmessage.go | 309 ++++++++++++++++++ vendor/modules.txt | 1 + 16 files changed, 330 insertions(+), 11 deletions(-) rename {pkg => client/pkg}/jsonmessage/jsonmessage.go (100%) rename {pkg => client/pkg}/jsonmessage/jsonmessage_test.go (100%) create mode 100644 vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go diff --git a/client/go.mod b/client/go.mod index 2140e03d85..fc355b7790 100644 --- a/client/go.mod +++ b/client/go.mod @@ -8,7 +8,9 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 github.com/distribution/reference v0.6.0 github.com/docker/go-connections v0.5.0 + github.com/docker/go-units v0.5.0 github.com/moby/moby/api v0.0.0 + github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/pkg/errors v0.9.1 @@ -18,7 +20,7 @@ require ( ) require ( - github.com/docker/go-units v0.5.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/client/go.sum b/client/go.sum index 9ee3ed829a..7228e52160 100644 --- a/client/go.sum +++ b/client/go.sum @@ -1,9 +1,13 @@ +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -25,6 +29,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -49,6 +55,7 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/jsonmessage/jsonmessage.go b/client/pkg/jsonmessage/jsonmessage.go similarity index 100% rename from pkg/jsonmessage/jsonmessage.go rename to client/pkg/jsonmessage/jsonmessage.go diff --git a/pkg/jsonmessage/jsonmessage_test.go b/client/pkg/jsonmessage/jsonmessage_test.go similarity index 100% rename from pkg/jsonmessage/jsonmessage_test.go rename to client/pkg/jsonmessage/jsonmessage_test.go diff --git a/integration/build/build_cgroupns_linux_test.go b/integration/build/build_cgroupns_linux_test.go index b3aead7f06..daae97ded6 100644 --- a/integration/build/build_cgroupns_linux_test.go +++ b/integration/build/build_cgroupns_linux_test.go @@ -8,11 +8,11 @@ import ( "testing" "github.com/docker/docker/integration/internal/requirement" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/daemon" "github.com/docker/docker/testutil/fakecontext" "github.com/moby/moby/api/types/build" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" "gotest.tools/v3/skip" ) diff --git a/integration/build/build_test.go b/integration/build/build_test.go index aaab7f7879..0c74bd1b6f 100644 --- a/integration/build/build_test.go +++ b/integration/build/build_test.go @@ -15,7 +15,6 @@ import ( "time" cerrdefs "github.com/containerd/errdefs" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/fakecontext" "github.com/moby/moby/api/types/build" @@ -23,6 +22,7 @@ import ( "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/image" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" diff --git a/integration/build/build_userns_linux_test.go b/integration/build/build_userns_linux_test.go index 4a4272ce8e..96019c59eb 100644 --- a/integration/build/build_userns_linux_test.go +++ b/integration/build/build_userns_linux_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/docker/docker/integration/internal/container" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/daemon" "github.com/docker/docker/testutil/fakecontext" @@ -18,6 +17,7 @@ import ( "github.com/moby/moby/api/pkg/stdcopy" "github.com/moby/moby/api/types/build" containertypes "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" "gotest.tools/v3/poll" "gotest.tools/v3/skip" diff --git a/integration/container/copy_test.go b/integration/container/copy_test.go index c698a449c2..18011b9fff 100644 --- a/integration/container/copy_test.go +++ b/integration/container/copy_test.go @@ -14,11 +14,11 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/docker/docker/integration/internal/container" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil/fakecontext" "github.com/moby/go-archive" "github.com/moby/moby/api/types/build" containertypes "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" diff --git a/integration/container/export_test.go b/integration/container/export_test.go index a6054ae4a1..b111b3e869 100644 --- a/integration/container/export_test.go +++ b/integration/container/export_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/docker/docker/integration/internal/container" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/daemon" "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/image" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/poll" diff --git a/integration/internal/build/build.go b/integration/internal/build/build.go index c202375d0b..dfe90ab8c3 100644 --- a/integration/internal/build/build.go +++ b/integration/internal/build/build.go @@ -6,11 +6,11 @@ import ( "io" "testing" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil/fakecontext" "github.com/moby/moby/api/types/build" "github.com/moby/moby/api/types/image" "github.com/moby/moby/client" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" ) diff --git a/integration/internal/image/load.go b/integration/internal/image/load.go index 27d7c1f437..d85376bef0 100644 --- a/integration/internal/image/load.go +++ b/integration/internal/image/load.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/docker/docker/internal/testutils/specialimage" - "github.com/docker/docker/pkg/jsonmessage" "github.com/moby/go-archive" "github.com/moby/moby/client" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" ) diff --git a/integration/plugin/common/plugin_test.go b/integration/plugin/common/plugin_test.go index 869180abac..4bf5f391fa 100644 --- a/integration/plugin/common/plugin_test.go +++ b/integration/plugin/common/plugin_test.go @@ -15,7 +15,6 @@ import ( c8dimages "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/remotes/docker" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/daemon" "github.com/docker/docker/testutil/fixtures/plugin" @@ -25,6 +24,7 @@ import ( registrytypes "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/system" "github.com/moby/moby/client" + "github.com/moby/moby/client/pkg/jsonmessage" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" diff --git a/integration/system/event_test.go b/integration/system/event_test.go index c7648ca02c..4c50ade5c6 100644 --- a/integration/system/event_test.go +++ b/integration/system/event_test.go @@ -12,13 +12,13 @@ import ( "time" "github.com/docker/docker/integration/internal/container" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/testutil/request" containertypes "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/mount" "github.com/moby/moby/api/types/volume" + "github.com/moby/moby/client/pkg/jsonmessage" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" diff --git a/testutil/fixtures/load/frozen.go b/testutil/fixtures/load/frozen.go index d3d297ed90..d14a10144d 100644 --- a/testutil/fixtures/load/frozen.go +++ b/testutil/fixtures/load/frozen.go @@ -10,9 +10,9 @@ import ( "strings" "sync" - "github.com/docker/docker/pkg/jsonmessage" "github.com/moby/moby/api/types/image" "github.com/moby/moby/client" + "github.com/moby/moby/client/pkg/jsonmessage" "github.com/moby/term" "github.com/pkg/errors" "go.opentelemetry.io/otel" diff --git a/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go b/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go new file mode 100644 index 0000000000..4ca5692d7a --- /dev/null +++ b/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go @@ -0,0 +1,309 @@ +package jsonmessage + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "time" + + "github.com/docker/go-units" + "github.com/moby/moby/api/types/jsonstream" + "github.com/moby/term" +) + +// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to +// ensure the formatted time isalways the same number of characters. +const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + +// JSONProgress describes a progress message in a JSON stream. +type JSONProgress struct { + jsonstream.Progress + + // terminalFd is the fd of the current terminal, if any. It is used + // to get the terminal width. + terminalFd uintptr + + // nowFunc is used to override the current time in tests. + nowFunc func() time.Time + + // winSize is used to override the terminal width in tests. + winSize int +} + +func (p *JSONProgress) String() string { + var ( + width = p.width() + pbBox string + numbersBox string + ) + if p.Current <= 0 && p.Total <= 0 { + return "" + } + if p.Total <= 0 { + switch p.Units { + case "": + return fmt.Sprintf("%8v", units.HumanSize(float64(p.Current))) + default: + return fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 + if percentage > 50 { + percentage = 50 + } + if width > 110 { + // this number can't be negative gh#7136 + numSpaces := 0 + if 50-percentage > 0 { + numSpaces = 50 - percentage + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } + + switch { + case p.HideCounts: + case p.Units == "": // no units, use bytes + current := units.HumanSize(float64(p.Current)) + total := units.HumanSize(float64(p.Total)) + + numbersBox = fmt.Sprintf("%8v/%v", current, total) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%8v", current) + } + default: + numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + // Show approximation of remaining time if there's enough width. + var timeLeftBox string + if width > 50 { + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := p.now().Sub(time.Unix(p.Start, 0)) + perEntry := fromStart / time.Duration(p.Current) + left := time.Duration(p.Total-p.Current) * perEntry + timeLeftBox = " " + left.Round(time.Second).String() + } + } + return pbBox + numbersBox + timeLeftBox +} + +// now returns the current time in UTC, but can be overridden in tests +// by setting JSONProgress.nowFunc to a custom function. +func (p *JSONProgress) now() time.Time { + if p.nowFunc != nil { + return p.nowFunc() + } + return time.Now().UTC() +} + +// width returns the current terminal's width, but can be overridden +// in tests by setting JSONProgress.winSize to a non-zero value. +func (p *JSONProgress) width() int { + if p.winSize != 0 { + return p.winSize + } + ws, err := term.GetWinsize(p.terminalFd) + if err == nil { + return int(ws.Width) + } + return 200 +} + +// JSONMessage defines a message struct. It describes +// the created time, where it from, status, ID of the +// message. It's used for docker events. +type JSONMessage struct { + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + Progress *JSONProgress `json:"progressDetail,omitempty"` + + // ProgressMessage is a pre-formatted presentation of [Progress]. + // + // Deprecated: this field is deprecated since docker v0.7.1 / API v1.8. Use the information in [Progress] instead. This field will be omitted in a future release. + ProgressMessage string `json:"progress,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` + Error *jsonstream.Error `json:"errorDetail,omitempty"` + + // ErrorMessage contains errors encountered during the operation. + // + // Deprecated: this field is deprecated since docker v0.6.0 / API v1.4. Use [Error.Message] instead. This field will be omitted in a future release. + ErrorMessage string `json:"error,omitempty"` // deprecated + // Aux contains out-of-band data, such as digests for push signing and image id after building. + Aux *json.RawMessage `json:"aux,omitempty"` +} + +// We can probably use [aec.EmptyBuilder] for managing the output, but +// currently we're doing it all manually, so defining some consts for +// the basics we use. +// +// [aec.EmptyBuilder]: https://pkg.go.dev/github.com/morikuni/aec#EmptyBuilder +const ( + ansiEraseLine = "\x1b[2K" // Erase entire line + ansiCursorUpFmt = "\x1b[%dA" // Move cursor up N lines + ansiCursorDownFmt = "\x1b[%dB" // Move cursor down N lines +) + +func clearLine(out io.Writer) { + _, _ = out.Write([]byte(ansiEraseLine)) +} + +func cursorUp(out io.Writer, l uint) { + if l == 0 { + return + } + _, _ = fmt.Fprintf(out, ansiCursorUpFmt, l) +} + +func cursorDown(out io.Writer, l uint) { + if l == 0 { + return + } + _, _ = fmt.Fprintf(out, ansiCursorDownFmt, l) +} + +// Display prints the JSONMessage to out. If isTerminal is true, it erases +// the entire current line when displaying the progressbar. It returns an +// error if the [JSONMessage.Error] field is non-nil. +func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { + if jm.Error != nil { + return jm.Error + } + var endl string + if isTerminal && jm.Stream == "" && jm.Progress != nil { + clearLine(out) + endl = "\r" + _, _ = fmt.Fprint(out, endl) + } else if jm.Progress != nil && jm.Progress.String() != "" { // disable progressbar in non-terminal + return nil + } + if jm.TimeNano != 0 { + _, _ = fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed)) + } else if jm.Time != 0 { + _, _ = fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed)) + } + if jm.ID != "" { + _, _ = fmt.Fprintf(out, "%s: ", jm.ID) + } + if jm.From != "" { + _, _ = fmt.Fprintf(out, "(from %s) ", jm.From) + } + if jm.Progress != nil && isTerminal { + _, _ = fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) + } else if jm.ProgressMessage != "" { // deprecated + _, _ = fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) + } else if jm.Stream != "" { + _, _ = fmt.Fprintf(out, "%s%s", jm.Stream, endl) + } else { + _, _ = fmt.Fprintf(out, "%s%s\n", jm.Status, endl) + } + return nil +} + +// DisplayJSONMessagesStream reads a JSON message stream from in, and writes +// each [JSONMessage] to out. It returns an error if an invalid JSONMessage +// is received, or if a JSONMessage containers a non-zero [JSONMessage.Error]. +// +// Presentation of the JSONMessage depends on whether a terminal is attached, +// and on the terminal width. Progress bars ([JSONProgress]) are suppressed +// on narrower terminals (< 110 characters). +// +// - isTerminal describes if out is a terminal, in which case it prints +// a newline ("\n") at the end of each line and moves the cursor while +// displaying. +// - terminalFd is the fd of the current terminal (if any), and used +// to get the terminal width. +// - auxCallback allows handling the [JSONMessage.Aux] field. It is +// called if a JSONMessage contains an Aux field, in which case +// DisplayJSONMessagesStream does not present the JSONMessage. +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error { + var ( + dec = json.NewDecoder(in) + ids = make(map[string]uint) + ) + + for { + var diff uint + var jm JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + + if jm.Aux != nil { + if auxCallback != nil { + auxCallback(jm) + } + continue + } + + if jm.Progress != nil { + jm.Progress.terminalFd = terminalFd + } + if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { + line, ok := ids[jm.ID] + if !ok { + // NOTE: This approach of using len(id) to + // figure out the number of lines of history + // only works as long as we clear the history + // when we output something that's not + // accounted for in the map, such as a line + // with no ID. + line = uint(len(ids)) + ids[jm.ID] = line + if isTerminal { + _, _ = fmt.Fprintf(out, "\n") + } + } + diff = uint(len(ids)) - line + if isTerminal { + cursorUp(out, diff) + } + } else { + // When outputting something that isn't progress + // output, clear the history of previous lines. We + // don't want progress entries from some previous + // operation to be updated (for example, pull -a + // with multiple tags). + ids = make(map[string]uint) + } + err := jm.Display(out, isTerminal) + if jm.ID != "" && isTerminal { + cursorDown(out, diff) + } + if err != nil { + return err + } + } + return nil +} + +// Stream is an io.Writer for output with utilities to get the output's file +// descriptor and to detect whether it's a terminal. +// +// it is subset of the streams.Out type in +// https://pkg.go.dev/github.com/docker/cli@v20.10.17+incompatible/cli/streams#Out +type Stream interface { + io.Writer + FD() uintptr + IsTerminal() bool +} + +// DisplayJSONMessagesToStream prints json messages to the output Stream. It is +// used by the Docker CLI to print JSONMessage streams. +func DisplayJSONMessagesToStream(in io.Reader, stream Stream, auxCallback func(JSONMessage)) error { + return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 07891dd0f5..0dd902a862 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -966,6 +966,7 @@ github.com/moby/moby/api/types/volume # github.com/moby/moby/client v0.0.0 => ./client ## explicit; go 1.23.0 github.com/moby/moby/client +github.com/moby/moby/client/pkg/jsonmessage github.com/moby/moby/client/pkg/stringid # github.com/moby/patternmatcher v0.6.0 ## explicit; go 1.19