github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/pkg/jsonmessage/jsonmessage.go (about) 1 package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage" 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "strings" 9 "time" 10 11 gotty "github.com/Nvveen/Gotty" 12 "github.com/docker/docker/pkg/term" 13 units "github.com/docker/go-units" 14 ) 15 16 // RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to 17 // ensure the formatted time isalways the same number of characters. 18 const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" 19 20 // JSONError wraps a concrete Code and Message, `Code` is 21 // is an integer error code, `Message` is the error message. 22 type JSONError struct { 23 Code int `json:"code,omitempty"` 24 Message string `json:"message,omitempty"` 25 } 26 27 func (e *JSONError) Error() string { 28 return e.Message 29 } 30 31 // JSONProgress describes a Progress. terminalFd is the fd of the current terminal, 32 // Start is the initial value for the operation. Current is the current status and 33 // value of the progress made towards Total. Total is the end value describing when 34 // we made 100% progress for an operation. 35 type JSONProgress struct { 36 terminalFd uintptr 37 Current int64 `json:"current,omitempty"` 38 Total int64 `json:"total,omitempty"` 39 Start int64 `json:"start,omitempty"` 40 // If true, don't show xB/yB 41 HideCounts bool `json:"hidecounts,omitempty"` 42 Units string `json:"units,omitempty"` 43 nowFunc func() time.Time 44 winSize int 45 } 46 47 func (p *JSONProgress) String() string { 48 var ( 49 width = p.width() 50 pbBox string 51 numbersBox string 52 timeLeftBox string 53 ) 54 if p.Current <= 0 && p.Total <= 0 { 55 return "" 56 } 57 if p.Total <= 0 { 58 switch p.Units { 59 case "": 60 current := units.HumanSize(float64(p.Current)) 61 return fmt.Sprintf("%8v", current) 62 default: 63 return fmt.Sprintf("%d %s", p.Current, p.Units) 64 } 65 } 66 67 percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 68 if percentage > 50 { 69 percentage = 50 70 } 71 if width > 110 { 72 // this number can't be negative gh#7136 73 numSpaces := 0 74 if 50-percentage > 0 { 75 numSpaces = 50 - percentage 76 } 77 pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) 78 } 79 80 switch { 81 case p.HideCounts: 82 case p.Units == "": // no units, use bytes 83 current := units.HumanSize(float64(p.Current)) 84 total := units.HumanSize(float64(p.Total)) 85 86 numbersBox = fmt.Sprintf("%8v/%v", current, total) 87 88 if p.Current > p.Total { 89 // remove total display if the reported current is wonky. 90 numbersBox = fmt.Sprintf("%8v", current) 91 } 92 default: 93 numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units) 94 95 if p.Current > p.Total { 96 // remove total display if the reported current is wonky. 97 numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units) 98 } 99 } 100 101 if p.Current > 0 && p.Start > 0 && percentage < 50 { 102 fromStart := p.now().Sub(time.Unix(p.Start, 0)) 103 perEntry := fromStart / time.Duration(p.Current) 104 left := time.Duration(p.Total-p.Current) * perEntry 105 left = (left / time.Second) * time.Second 106 107 if width > 50 { 108 timeLeftBox = " " + left.String() 109 } 110 } 111 return pbBox + numbersBox + timeLeftBox 112 } 113 114 // shim for testing 115 func (p *JSONProgress) now() time.Time { 116 if p.nowFunc == nil { 117 p.nowFunc = func() time.Time { 118 return time.Now().UTC() 119 } 120 } 121 return p.nowFunc() 122 } 123 124 // shim for testing 125 func (p *JSONProgress) width() int { 126 if p.winSize != 0 { 127 return p.winSize 128 } 129 ws, err := term.GetWinsize(p.terminalFd) 130 if err == nil { 131 return int(ws.Width) 132 } 133 return 200 134 } 135 136 // JSONMessage defines a message struct. It describes 137 // the created time, where it from, status, ID of the 138 // message. It's used for docker events. 139 type JSONMessage struct { 140 Stream string `json:"stream,omitempty"` 141 Status string `json:"status,omitempty"` 142 Progress *JSONProgress `json:"progressDetail,omitempty"` 143 ProgressMessage string `json:"progress,omitempty"` //deprecated 144 ID string `json:"id,omitempty"` 145 From string `json:"from,omitempty"` 146 Time int64 `json:"time,omitempty"` 147 TimeNano int64 `json:"timeNano,omitempty"` 148 Error *JSONError `json:"errorDetail,omitempty"` 149 ErrorMessage string `json:"error,omitempty"` //deprecated 150 // Aux contains out-of-band data, such as digests for push signing and image id after building. 151 Aux *json.RawMessage `json:"aux,omitempty"` 152 } 153 154 /* Satisfied by gotty.TermInfo as well as noTermInfo from below */ 155 type termInfo interface { 156 Parse(attr string, params ...interface{}) (string, error) 157 } 158 159 type noTermInfo struct{} // canary used when no terminfo. 160 161 func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) { 162 return "", fmt.Errorf("noTermInfo") 163 } 164 165 func clearLine(out io.Writer, ti termInfo) { 166 // el2 (clear whole line) is not exposed by terminfo. 167 168 // First clear line from beginning to cursor 169 if attr, err := ti.Parse("el1"); err == nil { 170 fmt.Fprintf(out, "%s", attr) 171 } else { 172 fmt.Fprintf(out, "\x1b[1K") 173 } 174 // Then clear line from cursor to end 175 if attr, err := ti.Parse("el"); err == nil { 176 fmt.Fprintf(out, "%s", attr) 177 } else { 178 fmt.Fprintf(out, "\x1b[K") 179 } 180 } 181 182 func cursorUp(out io.Writer, ti termInfo, l int) { 183 if l == 0 { // Should never be the case, but be tolerant 184 return 185 } 186 if attr, err := ti.Parse("cuu", l); err == nil { 187 fmt.Fprintf(out, "%s", attr) 188 } else { 189 fmt.Fprintf(out, "\x1b[%dA", l) 190 } 191 } 192 193 func cursorDown(out io.Writer, ti termInfo, l int) { 194 if l == 0 { // Should never be the case, but be tolerant 195 return 196 } 197 if attr, err := ti.Parse("cud", l); err == nil { 198 fmt.Fprintf(out, "%s", attr) 199 } else { 200 fmt.Fprintf(out, "\x1b[%dB", l) 201 } 202 } 203 204 // Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out` 205 // is a terminal. If this is the case, it will erase the entire current line 206 // when displaying the progressbar. 207 func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error { 208 if jm.Error != nil { 209 if jm.Error.Code == 401 { 210 return fmt.Errorf("authentication is required") 211 } 212 return jm.Error 213 } 214 var endl string 215 if termInfo != nil && jm.Stream == "" && jm.Progress != nil { 216 clearLine(out, termInfo) 217 endl = "\r" 218 fmt.Fprintf(out, endl) 219 } else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal 220 return nil 221 } 222 if jm.TimeNano != 0 { 223 fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed)) 224 } else if jm.Time != 0 { 225 fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed)) 226 } 227 if jm.ID != "" { 228 fmt.Fprintf(out, "%s: ", jm.ID) 229 } 230 if jm.From != "" { 231 fmt.Fprintf(out, "(from %s) ", jm.From) 232 } 233 if jm.Progress != nil && termInfo != nil { 234 fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) 235 } else if jm.ProgressMessage != "" { //deprecated 236 fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) 237 } else if jm.Stream != "" { 238 fmt.Fprintf(out, "%s%s", jm.Stream, endl) 239 } else { 240 fmt.Fprintf(out, "%s%s\n", jm.Status, endl) 241 } 242 return nil 243 } 244 245 // DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal` 246 // describes if `out` is a terminal. If this is the case, it will print `\n` at the end of 247 // each line and move the cursor while displaying. 248 func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error { 249 var ( 250 dec = json.NewDecoder(in) 251 ids = make(map[string]int) 252 ) 253 254 var termInfo termInfo 255 256 if isTerminal { 257 term := os.Getenv("TERM") 258 if term == "" { 259 term = "vt102" 260 } 261 262 var err error 263 if termInfo, err = gotty.OpenTermInfo(term); err != nil { 264 termInfo = &noTermInfo{} 265 } 266 } 267 268 for { 269 diff := 0 270 var jm JSONMessage 271 if err := dec.Decode(&jm); err != nil { 272 if err == io.EOF { 273 break 274 } 275 return err 276 } 277 278 if jm.Aux != nil { 279 if auxCallback != nil { 280 auxCallback(jm.Aux) 281 } 282 continue 283 } 284 285 if jm.Progress != nil { 286 jm.Progress.terminalFd = terminalFd 287 } 288 if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { 289 line, ok := ids[jm.ID] 290 if !ok { 291 // NOTE: This approach of using len(id) to 292 // figure out the number of lines of history 293 // only works as long as we clear the history 294 // when we output something that's not 295 // accounted for in the map, such as a line 296 // with no ID. 297 line = len(ids) 298 ids[jm.ID] = line 299 if termInfo != nil { 300 fmt.Fprintf(out, "\n") 301 } 302 } 303 diff = len(ids) - line 304 if termInfo != nil { 305 cursorUp(out, termInfo, diff) 306 } 307 } else { 308 // When outputting something that isn't progress 309 // output, clear the history of previous lines. We 310 // don't want progress entries from some previous 311 // operation to be updated (for example, pull -a 312 // with multiple tags). 313 ids = make(map[string]int) 314 } 315 err := jm.Display(out, termInfo) 316 if jm.ID != "" && termInfo != nil { 317 cursorDown(out, termInfo, diff) 318 } 319 if err != nil { 320 return err 321 } 322 } 323 return nil 324 } 325 326 type stream interface { 327 io.Writer 328 FD() uintptr 329 IsTerminal() bool 330 } 331 332 // DisplayJSONMessagesToStream prints json messages to the output stream 333 func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error { 334 return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) 335 }