github.com/moby/docker@v26.1.3+incompatible/pkg/streamformatter/streamformatter.go (about) 1 // Package streamformatter provides helper functions to format a stream. 2 package streamformatter // import "github.com/docker/docker/pkg/streamformatter" 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "io" 8 "sync" 9 10 "github.com/docker/docker/pkg/jsonmessage" 11 "github.com/docker/docker/pkg/progress" 12 ) 13 14 const streamNewline = "\r\n" 15 16 type jsonProgressFormatter struct{} 17 18 func appendNewline(source []byte) []byte { 19 return append(source, []byte(streamNewline)...) 20 } 21 22 // FormatStatus formats the specified objects according to the specified format (and id). 23 func FormatStatus(id, format string, a ...interface{}) []byte { 24 str := fmt.Sprintf(format, a...) 25 b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str}) 26 if err != nil { 27 return FormatError(err) 28 } 29 return appendNewline(b) 30 } 31 32 // FormatError formats the error as a JSON object 33 func FormatError(err error) []byte { 34 jsonError, ok := err.(*jsonmessage.JSONError) 35 if !ok { 36 jsonError = &jsonmessage.JSONError{Message: err.Error()} 37 } 38 if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { 39 return appendNewline(b) 40 } 41 return []byte(`{"error":"format error"}` + streamNewline) 42 } 43 44 func (sf *jsonProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte { 45 return FormatStatus(id, format, a...) 46 } 47 48 // formatProgress formats the progress information for a specified action. 49 func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { 50 if progress == nil { 51 progress = &jsonmessage.JSONProgress{} 52 } 53 var auxJSON *json.RawMessage 54 if aux != nil { 55 auxJSONBytes, err := json.Marshal(aux) 56 if err != nil { 57 return nil 58 } 59 auxJSON = new(json.RawMessage) 60 *auxJSON = auxJSONBytes 61 } 62 b, err := json.Marshal(&jsonmessage.JSONMessage{ 63 Status: action, 64 ProgressMessage: progress.String(), 65 Progress: progress, 66 ID: id, 67 Aux: auxJSON, 68 }) 69 if err != nil { 70 return nil 71 } 72 return appendNewline(b) 73 } 74 75 type rawProgressFormatter struct{} 76 77 func (sf *rawProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte { 78 return []byte(fmt.Sprintf(format, a...) + streamNewline) 79 } 80 81 func (sf *rawProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { 82 if progress == nil { 83 progress = &jsonmessage.JSONProgress{} 84 } 85 endl := "\r" 86 if progress.String() == "" { 87 endl += "\n" 88 } 89 return []byte(action + " " + progress.String() + endl) 90 } 91 92 // NewProgressOutput returns a progress.Output object that can be passed to 93 // progress.NewProgressReader. 94 func NewProgressOutput(out io.Writer) progress.Output { 95 return &progressOutput{sf: &rawProgressFormatter{}, out: out, newLines: true} 96 } 97 98 // NewJSONProgressOutput returns a progress.Output that formats output 99 // using JSON objects 100 func NewJSONProgressOutput(out io.Writer, newLines bool) progress.Output { 101 return &progressOutput{sf: &jsonProgressFormatter{}, out: out, newLines: newLines} 102 } 103 104 type formatProgress interface { 105 formatStatus(id, format string, a ...interface{}) []byte 106 formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte 107 } 108 109 type progressOutput struct { 110 sf formatProgress 111 out io.Writer 112 newLines bool 113 mu sync.Mutex 114 } 115 116 // WriteProgress formats progress information from a ProgressReader. 117 func (out *progressOutput) WriteProgress(prog progress.Progress) error { 118 var formatted []byte 119 if prog.Message != "" { 120 formatted = out.sf.formatStatus(prog.ID, prog.Message) 121 } else { 122 jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total, HideCounts: prog.HideCounts, Units: prog.Units} 123 formatted = out.sf.formatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux) 124 } 125 126 out.mu.Lock() 127 defer out.mu.Unlock() 128 _, err := out.out.Write(formatted) 129 if err != nil { 130 return err 131 } 132 133 if out.newLines && prog.LastUpdate { 134 _, err = out.out.Write(out.sf.formatStatus("", "")) 135 return err 136 } 137 138 return nil 139 } 140 141 // AuxFormatter is a streamFormatter that writes aux progress messages 142 type AuxFormatter struct { 143 io.Writer 144 } 145 146 // Emit emits the given interface as an aux progress message 147 func (sf *AuxFormatter) Emit(id string, aux interface{}) error { 148 auxJSONBytes, err := json.Marshal(aux) 149 if err != nil { 150 return err 151 } 152 auxJSON := new(json.RawMessage) 153 *auxJSON = auxJSONBytes 154 msgJSON, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Aux: auxJSON}) 155 if err != nil { 156 return err 157 } 158 msgJSON = appendNewline(msgJSON) 159 n, err := sf.Writer.Write(msgJSON) 160 if n != len(msgJSON) { 161 return io.ErrShortWrite 162 } 163 return err 164 }