Skip to content

driver/docker-container: remove uses of jsonmessage#3777

Draft
thaJeztah wants to merge 1 commit intodocker:masterfrom
thaJeztah:no_jsonmessage
Draft

driver/docker-container: remove uses of jsonmessage#3777
thaJeztah wants to merge 1 commit intodocker:masterfrom
thaJeztah:no_jsonmessage

Conversation

@thaJeztah
Copy link
Copy Markdown
Member

This function was always using io.Discard for printing the progress, so we can use the Wait() method, which reads the stream, returning any error (similar to jsonmessage.DisplayJSONMessagesStream), and closes the stream either if the context is cancelled, or if the stream ends.

This function was always using `io.Discard` for printing the progress,
so we can use the `Wait()` method, which reads the stream, returning
any error (similar to jsonmessage.DisplayJSONMessagesStream), and
closes the stream either if the context is cancelled, or if the
stream ends.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
return err
}
defer resp.Close()
return jsonmessage.DisplayJSONMessagesStream(resp, io.Discard, 0, false, nil)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#2988 changed this code path to explicitly read the pull JSON stream and return any errorDetail entries from the daemon.

That behavior matters because images/create can return 200 OK and then report the real failure later in the streamed JSON response, which is the bug fixed in moby/moby#49453

The current implementation still preserves that because DisplayJSONMessagesStream() returns jm.Error from the stream:

  • // 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 Display(jm jsonstream.Message, out io.Writer, isTerminal bool, width uint16) 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.Current > 0 || jm.Progress.Total > 0) { // disable progressbar in non-terminal
    return nil
    }
    if jm.ID != "" {
    _, _ = fmt.Fprintf(out, "%s: ", jm.ID)
    }
    if jm.Progress != nil && isTerminal {
    if width == 0 {
    width = 200
    }
    _, _ = fmt.Fprintf(out, "%s %s%s", jm.Status, RenderTUIProgress(*jm.Progress, width), 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
  • func DisplayJSONMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
    ids := make(map[string]uint)
    var width uint16 = 200
    if isTerminal {
    ws, err := term.GetWinsize(terminalFd)
    if err == nil {
    width = ws.Width
    }
    }
    for jm, err := range messages {
    var diff uint
    if err != nil {
    return err
    }

By contrast, resp.Wait(ctx) only returns decoder/context errors and does not inspect jsonstream.Message.Error at all:

  • // JSONMessages decodes the response stream as a sequence of JSONMessages.
    // if stream ends or context is cancelled, the underlying [io.Reader] is closed.
    func (r Stream) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error] {
    stop := context.AfterFunc(ctx, func() {
    _ = r.Close()
    })
    dec := json.NewDecoder(r)
    return func(yield func(jsonstream.Message, error) bool) {
    defer func() {
    stop() // unregister AfterFunc
    r.Close()
    }()
    for {
    var jm jsonstream.Message
    err := dec.Decode(&jm)
    if errors.Is(err, io.EOF) {
    break
    }
    if ctx.Err() != nil {
    yield(jm, ctx.Err())
    return
    }
    if !yield(jm, err) {
    return
    }
    }
    }
    }
    // Wait waits for operation to complete and detects errors reported as JSONMessage
    func (r Stream) Wait(ctx context.Context) error {
    for _, err := range r.JSONMessages(ctx) {
    if err != nil {
    return err
    }
    }
    return nil
  • type Message struct {
    Stream string `json:"stream,omitempty"`
    Status string `json:"status,omitempty"`
    Progress *Progress `json:"progressDetail,omitempty"`
    ID string `json:"id,omitempty"`
    Error *Error `json:"errorDetail,omitempty"`
    Aux *json.RawMessage `json:"aux,omitempty"` // Aux contains out-of-band data, such as digests for push signing and image id after building.

So this change looks like a regression of #2988: pull failures reported via streamed errorDetail would be silently ignored again, and bootstrap would fall through to a later, less accurate error.

@thaJeztah
Copy link
Copy Markdown
Member Author

By contrast, resp.Wait(ctx) only returns decoder/context errors and does not inspect jsonstream.Message.Error at all:

Hm.. good catch; I think that's a bug!

Let me move this one to draft, and look at that implementation.

@thaJeztah thaJeztah marked this pull request as draft April 3, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants