api/types/jsonstream: define Message type

The schema of a JSON-stream message is very pertinent to the api module.
Provide a canonical definition in the api module and refactor the daemon
code to use it. Drop the long-deprecated ErrorMessage field from the API
definition, but have the daemon continue to emit it for compatibility
with docker-py v7.1.0.

Co-authored-by: Cory Snider <csnider@mirantis.com>
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
This commit is contained in:
Cory Snider
2025-10-09 18:06:04 -04:00
committed by Austin Vazquez
parent ae28867804
commit 687c3d7f42
8 changed files with 53 additions and 56 deletions

View File

@@ -0,0 +1,15 @@
package jsonstream
import "encoding/json"
// 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 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.
}

View File

@@ -14,25 +14,6 @@ import (
"github.com/moby/moby/client/pkg/progress"
)
// jsonMessage defines a message struct. It describes
// the created time, where it from, status, ID of the
// message. It's used for docker events.
//
// It is a reduced set of [jsonmessage.JSONMessage].
type jsonMessage struct {
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
Progress *jsonstream.Progress `json:"progressDetail,omitempty"`
ID string `json:"id,omitempty"`
Error *jsonstream.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.
// 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
}
const streamNewline = "\r\n"
type jsonProgressFormatter struct{}
@@ -44,7 +25,7 @@ func appendNewline(source []byte) []byte {
// FormatStatus formats the specified objects according to the specified format (and id).
func FormatStatus(id, format string, a ...any) []byte {
str := fmt.Sprintf(format, a...)
b, err := json.Marshal(&jsonMessage{ID: id, Status: str})
b, err := json.Marshal(&jsonstream.Message{ID: id, Status: str})
if err != nil {
return FormatError(err)
}
@@ -57,7 +38,7 @@ func FormatError(err error) []byte {
if !ok {
jsonError = &jsonstream.Error{Message: err.Error()}
}
if b, err := json.Marshal(&jsonMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
if b, err := json.Marshal(&jsonstream.Message{Error: jsonError}); err == nil {
return appendNewline(b)
}
return []byte(`{"error":"format error"}` + streamNewline)
@@ -81,7 +62,7 @@ func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jso
auxJSON = new(json.RawMessage)
*auxJSON = auxJSONBytes
}
b, err := json.Marshal(&jsonMessage{
b, err := json.Marshal(&jsonstream.Message{
Status: action,
Progress: progress,
ID: id,
@@ -234,7 +215,7 @@ func (sf *AuxFormatter) Emit(id string, aux any) error {
}
auxJSON := new(json.RawMessage)
*auxJSON = auxJSONBytes
msgJSON, err := json.Marshal(&jsonMessage{ID: id, Aux: auxJSON})
msgJSON, err := json.Marshal(&jsonstream.Message{ID: id, Aux: auxJSON})
if err != nil {
return err
}

View File

@@ -41,14 +41,14 @@ func TestFormatStatus(t *testing.T) {
func TestFormatError(t *testing.T) {
res := FormatError(errors.New("Error for formatter"))
expected := `{"errorDetail":{"message":"Error for formatter"},"error":"Error for formatter"}` + "\r\n"
expected := `{"errorDetail":{"message":"Error for formatter"}}` + "\r\n"
assert.Check(t, is.Equal(expected, string(res)))
}
func TestFormatJSONError(t *testing.T) {
err := &jsonstream.Error{Code: 50, Message: "Json error"}
res := FormatError(err)
expected := `{"errorDetail":{"code":50,"message":"Json error"},"error":"Json error"}` + streamNewline
expected := `{"errorDetail":{"code":50,"message":"Json error"}}` + streamNewline
assert.Check(t, is.Equal(expected, string(res)))
}
@@ -61,12 +61,12 @@ func TestJsonProgressFormatterFormatProgress(t *testing.T) {
}
aux := "aux message"
res := sf.formatProgress("id", "action", jsonProgress, aux)
msg := &jsonMessage{}
msg := &jsonstream.Message{}
assert.NilError(t, json.Unmarshal(res, msg))
rawAux := json.RawMessage(`"` + aux + `"`)
expected := &jsonMessage{
expected := &jsonstream.Message{
ID: "id",
Status: "action",
Aux: &rawAux,

View File

@@ -3,6 +3,8 @@ package streamformatter
import (
"encoding/json"
"io"
"github.com/moby/moby/api/types/jsonstream"
)
type streamWriter struct {
@@ -20,7 +22,7 @@ func (sw *streamWriter) Write(buf []byte) (int, error) {
}
func (sw *streamWriter) format(buf []byte) []byte {
msg := &jsonMessage{Stream: sw.lineFormat(buf)}
msg := &jsonstream.Message{Stream: sw.lineFormat(buf)}
b, err := json.Marshal(msg)
if err != nil {
return FormatError(err)

View File

@@ -8,28 +8,10 @@ import (
"sync"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/v2/daemon/internal/compat"
"github.com/moby/moby/v2/daemon/internal/progress"
)
// jsonMessage defines a message struct. It describes
// the created time, where it from, status, ID of the
// message. It's used for docker events.
//
// It is a reduced set of [jsonmessage.JSONMessage].
type jsonMessage struct {
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
Progress *jsonstream.Progress `json:"progressDetail,omitempty"`
ID string `json:"id,omitempty"`
Error *jsonstream.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.
// 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
}
const streamNewline = "\r\n"
type jsonProgressFormatter struct{}
@@ -41,7 +23,7 @@ func appendNewline(source []byte) []byte {
// FormatStatus formats the specified objects according to the specified format (and id).
func FormatStatus(id, format string, a ...any) []byte {
str := fmt.Sprintf(format, a...)
b, err := json.Marshal(&jsonMessage{ID: id, Status: str})
b, err := json.Marshal(&jsonstream.Message{ID: id, Status: str})
if err != nil {
return FormatError(err)
}
@@ -54,7 +36,7 @@ func FormatError(err error) []byte {
if !ok {
jsonError = &jsonstream.Error{Message: err.Error()}
}
if b, err := json.Marshal(&jsonMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
if b, err := json.Marshal(compat.Wrap(&jsonstream.Message{Error: jsonError}, compat.WithExtraFields(map[string]any{"error": jsonError.Error()}))); err == nil {
return appendNewline(b)
}
return []byte(`{"error":"format error"}` + streamNewline)
@@ -78,7 +60,7 @@ func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jso
auxJSON = new(json.RawMessage)
*auxJSON = auxJSONBytes
}
b, err := json.Marshal(&jsonMessage{
b, err := json.Marshal(&jsonstream.Message{
Status: action,
Progress: progress,
ID: id,
@@ -151,7 +133,7 @@ func (sf *AuxFormatter) Emit(id string, aux any) error {
}
auxJSON := new(json.RawMessage)
*auxJSON = auxJSONBytes
msgJSON, err := json.Marshal(&jsonMessage{ID: id, Aux: auxJSON})
msgJSON, err := json.Marshal(&jsonstream.Message{ID: id, Aux: auxJSON})
if err != nil {
return err
}

View File

@@ -20,14 +20,14 @@ func TestFormatStatus(t *testing.T) {
func TestFormatError(t *testing.T) {
res := FormatError(errors.New("Error for formatter"))
expected := `{"errorDetail":{"message":"Error for formatter"},"error":"Error for formatter"}` + "\r\n"
expected := `{"error":"Error for formatter","errorDetail":{"message":"Error for formatter"}}` + "\r\n"
assert.Check(t, is.Equal(expected, string(res)))
}
func TestFormatJSONError(t *testing.T) {
err := &jsonstream.Error{Code: 50, Message: "Json error"}
res := FormatError(err)
expected := `{"errorDetail":{"code":50,"message":"Json error"},"error":"Json error"}` + streamNewline
expected := `{"error":"Json error","errorDetail":{"code":50,"message":"Json error"}}` + streamNewline
assert.Check(t, is.Equal(expected, string(res)))
}
@@ -40,12 +40,12 @@ func TestJsonProgressFormatterFormatProgress(t *testing.T) {
}
aux := "aux message"
res := sf.formatProgress("id", "action", jsonProgress, aux)
msg := &jsonMessage{}
msg := &jsonstream.Message{}
assert.NilError(t, json.Unmarshal(res, msg))
rawAux := json.RawMessage(`"` + aux + `"`)
expected := &jsonMessage{
expected := &jsonstream.Message{
ID: "id",
Status: "action",
Aux: &rawAux,

View File

@@ -3,6 +3,8 @@ package streamformatter
import (
"encoding/json"
"io"
"github.com/moby/moby/api/types/jsonstream"
)
type streamWriter struct {
@@ -20,7 +22,7 @@ func (sw *streamWriter) Write(buf []byte) (int, error) {
}
func (sw *streamWriter) format(buf []byte) []byte {
msg := &jsonMessage{Stream: sw.lineFormat(buf)}
msg := &jsonstream.Message{Stream: sw.lineFormat(buf)}
b, err := json.Marshal(msg)
if err != nil {
return FormatError(err)

View File

@@ -0,0 +1,15 @@
package jsonstream
import "encoding/json"
// 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 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.
}