github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/store/status.go (about) 1 /* 2 * Copyright 2019 The NATS Authors 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package store 17 18 import ( 19 "bytes" 20 "errors" 21 "fmt" 22 "net/http" 23 "reflect" 24 "strings" 25 ) 26 27 type StatusCode int 28 29 const ( 30 NONE StatusCode = iota 31 OK 32 WARN 33 ERR 34 ) 35 36 type PrintOption int 37 38 const ( 39 ALL PrintOption = iota 40 DetailsOnly 41 DetailsOnErrorOrWarning 42 ) 43 44 const okTemplate = "[ OK ] %s" 45 const warnTemplate = "[WARN] %s" 46 const errTemplate = "[ERR ] %s" 47 48 type Status interface { 49 Code() StatusCode 50 Message() string 51 } 52 53 type Summarizer interface { 54 Summary() (string, error) 55 } 56 57 type Formatter interface { 58 Format(indent string) string 59 } 60 61 type Report struct { 62 Label string 63 StatusCode StatusCode 64 Details []Status 65 Opt PrintOption 66 Data []byte 67 ReportSum bool 68 } 69 70 func IsReport(s Status) bool { 71 _, ok := s.(*Report) 72 return ok 73 } 74 75 func ToReport(s Status) *Report { 76 si, ok := s.(*Report) 77 if ok { 78 return si 79 } 80 return nil 81 } 82 83 func NewReport(code StatusCode, format string, args ...interface{}) *Report { 84 return &Report{StatusCode: code, Label: fmt.Sprintf(format, args...)} 85 } 86 87 func NewDetailedReport(summary bool) *Report { 88 return &Report{Opt: DetailsOnly, ReportSum: summary} 89 } 90 91 func FromError(err error) Status { 92 return &Report{StatusCode: ERR, Label: err.Error()} 93 } 94 95 func OKStatus(format string, args ...interface{}) Status { 96 return &Report{StatusCode: OK, Label: fmt.Sprintf(format, args...)} 97 } 98 99 func WarningStatus(format string, args ...interface{}) Status { 100 return &Report{StatusCode: WARN, Label: fmt.Sprintf(format, args...)} 101 } 102 103 func ErrorStatus(format string, args ...interface{}) Status { 104 return &Report{StatusCode: ERR, Label: fmt.Sprintf(format, args...)} 105 } 106 107 func (r *Report) AddStatus(code StatusCode, format string, args ...interface{}) *Report { 108 c := NewReport(code, format, args...) 109 r.Add(c) 110 return c 111 } 112 113 func (r *Report) AddOK(format string, args ...interface{}) *Report { 114 return r.AddStatus(OK, format, args...) 115 } 116 117 func (r *Report) AddWarning(format string, args ...interface{}) *Report { 118 return r.AddStatus(WARN, format, args...) 119 } 120 121 func (r *Report) AddError(format string, args ...interface{}) *Report { 122 return r.AddStatus(ERR, format, args...) 123 } 124 125 func (r *Report) AddFromError(err error) { 126 r.Add(FromError(err)) 127 } 128 129 func (r *Report) Add(status ...Status) { 130 for _, s := range status { 131 if s == nil || reflect.ValueOf(s).IsNil() { 132 continue 133 } 134 r.Details = append(r.Details, s) 135 } 136 r.updateCode() 137 } 138 139 func (r *Report) Code() StatusCode { 140 r.updateCode() 141 return r.StatusCode 142 } 143 144 func (r *Report) OK() bool { 145 r.updateCode() 146 return r.StatusCode == OK 147 } 148 149 func (r *Report) HasErrors() bool { 150 r.updateCode() 151 return r.StatusCode == ERR 152 } 153 154 func (r *Report) HasNoErrors() bool { 155 r.updateCode() 156 return r.StatusCode != ERR 157 } 158 159 func (r *Report) updateCode() StatusCode { 160 if len(r.Details) == 0 { 161 return r.StatusCode 162 } 163 r.StatusCode = NONE 164 for _, d := range r.Details { 165 if d == nil || reflect.ValueOf(d).IsNil() { 166 continue 167 } 168 cc := d.Code() 169 if cc > r.StatusCode { 170 r.StatusCode = cc 171 } 172 } 173 return r.StatusCode 174 } 175 176 func (r *Report) Message() string { 177 r.updateCode() 178 return r.Format("") 179 } 180 181 func (r *Report) printsSummary() bool { 182 switch r.Opt { 183 case ALL: 184 return true 185 case DetailsOnly: 186 return false 187 case DetailsOnErrorOrWarning: 188 return true 189 } 190 return false 191 } 192 193 func (r *Report) printsDetails() bool { 194 switch r.Opt { 195 case ALL: 196 return true 197 case DetailsOnly: 198 return true 199 case DetailsOnErrorOrWarning: 200 return r.StatusCode != OK || r.HasServerMessages() 201 } 202 return false 203 } 204 205 func (r *Report) HasServerMessages() bool { 206 for _, v := range r.Details { 207 if v == nil || reflect.ValueOf(v).IsNil() { 208 continue 209 } 210 if _, ok := v.(*ServerMessage); ok { 211 return true 212 } 213 } 214 return false 215 } 216 217 func (r *Report) Format(indent string) string { 218 var buf bytes.Buffer 219 var t string 220 switch r.StatusCode { 221 case NONE: 222 return "" 223 case OK: 224 t = okTemplate 225 case WARN: 226 t = warnTemplate 227 case ERR: 228 t = errTemplate 229 } 230 if r.printsSummary() { 231 m := fmt.Sprintf(t, r.Label) 232 m = IndentMessage(m, indent) 233 buf.WriteString(m) 234 if len(r.Details) > 0 && r.printsDetails() { 235 buf.WriteString(":\n") 236 indent = fmt.Sprintf("%s ", indent) 237 } 238 } 239 if r.printsDetails() { 240 for i, c := range r.Details { 241 if c == nil || reflect.ValueOf(c).IsNil() { 242 continue 243 } 244 if i > 0 { 245 buf.WriteRune('\n') 246 } 247 fm, ok := c.(Formatter) 248 if ok { 249 m := fm.Format(indent) 250 buf.WriteString(m) 251 } else { 252 m := c.Message() 253 m = IndentMessage(m, indent) 254 buf.WriteString(m) 255 } 256 } 257 } 258 return buf.String() 259 } 260 261 func (r *Report) Summary() (string, error) { 262 c := len(r.Details) 263 var ok, warn, err int 264 for _, j := range r.Details { 265 if j == nil || reflect.ValueOf(j).IsNil() { 266 c-- 267 continue 268 } 269 switch j.Code() { 270 case OK: 271 ok++ 272 case WARN: 273 warn++ 274 case ERR: 275 err++ 276 } 277 } 278 279 ov := "job" 280 if ok > 1 { 281 ov = "jobs" 282 } 283 wv := "warnings" 284 if warn > 1 { 285 wv = "warnings" 286 } 287 ev := "job" 288 if err > 1 { 289 ev = "jobs" 290 } 291 292 // always return an error if we failed 293 if err > 0 { 294 m := "all jobs failed" 295 if err != c { 296 m = fmt.Sprintf("%d %s failed - %d %s succeeded and %d had %s", err, ev, ok, ov, warn, wv) 297 } 298 return "", errors.New(m) 299 } 300 if r.ReportSum { 301 if ok == 1 && ok == c { 302 // report says it worked 303 return "", nil 304 } 305 if ok == c { 306 return "all jobs succeeded", nil 307 } 308 if warn == 1 && warn == c { 309 // report says it has a warning 310 return "", nil 311 } 312 if warn == c { 313 return "all jobs had warnings", nil 314 } 315 return fmt.Sprintf("%d %s succeeded - %d have %s", ok, ov, warn, wv), nil 316 } else { 317 return "", nil 318 } 319 } 320 321 func HoistChildren(s Status) []Status { 322 r, ok := s.(*Report) 323 if !ok { 324 return []Status{s} 325 } 326 if len(r.Details) == 0 { 327 return []Status{s} 328 } 329 return r.Details 330 } 331 332 type ServerMessage struct { 333 SrvMessage string 334 } 335 336 func NewServerMessage(format string, args ...interface{}) Status { 337 m := fmt.Sprintf(format, args...) 338 m = strings.TrimSpace(m) 339 return &ServerMessage{SrvMessage: m} 340 } 341 342 func (s *ServerMessage) Code() StatusCode { 343 return OK 344 } 345 346 func (s *ServerMessage) Message() string { 347 return s.Format("> ") 348 } 349 350 func (s *ServerMessage) Format(prefix string) string { 351 pf := fmt.Sprintf("%s> ", prefix) 352 return IndentMessage(s.SrvMessage, pf) 353 } 354 355 func IndentMessage(s string, prefix string) string { 356 lines := strings.Split(s, "\n") 357 for i, v := range lines { 358 vv := strings.TrimSpace(v) 359 if vv == "" { 360 continue 361 } 362 lines[i] = fmt.Sprintf("%s%s", prefix, v) 363 } 364 return strings.Join(lines, "\n") 365 } 366 367 func httpCodeToStatusCode(code int) StatusCode { 368 switch code { 369 case 0: 370 return NONE 371 case http.StatusOK: 372 return OK 373 case http.StatusCreated: 374 fallthrough 375 case http.StatusAccepted: 376 return WARN 377 default: 378 return ERR 379 } 380 } 381 382 func PushReport(code int, data []byte) Status { 383 r := NewDetailedReport(true) 384 r.Label = "push jwt to account server" 385 r.Opt = DetailsOnErrorOrWarning 386 sc := httpCodeToStatusCode(code) 387 m := "failed to push account to remote server" 388 switch sc { 389 case OK: 390 m = "pushed account jwt to the account server" 391 case WARN: 392 m = "pushed account jwt was accepted by the account server" 393 } 394 r.AddStatus(sc, m) 395 if len(data) > 0 { 396 r.Add(NewServerMessage(string(data))) 397 } 398 return r 399 } 400 401 func PullReport(code int, data []byte) Status { 402 r := NewDetailedReport(true) 403 r.Label = "pull jwt from account server" 404 r.Opt = DetailsOnErrorOrWarning 405 sc := httpCodeToStatusCode(code) 406 m := fmt.Sprintf("failed to pull jwt from the account server: : [%d - %s]", code, http.StatusText(code)) 407 switch sc { 408 case OK: 409 m = "pulled jwt from the account server" 410 default: 411 // nothing - didn't get this far 412 } 413 r.AddStatus(sc, m) 414 r.Data = data 415 return r 416 } 417 418 type Statuses []Status 419 420 func (ms Statuses) Message() string { 421 var buf bytes.Buffer 422 for _, s := range ms { 423 buf.WriteString(s.Message()) 424 } 425 return buf.String() 426 } 427 428 type JobStatus struct { 429 Warn string 430 OK string 431 Err error 432 } 433 434 func (js *JobStatus) Message() string { 435 if js.Err != nil { 436 return js.Err.Error() 437 } 438 if js.Warn != "" { 439 return js.Warn 440 } 441 return js.OK 442 } 443 444 type MultiJob []Status 445 446 func (mj MultiJob) Code() StatusCode { 447 code := NONE 448 for _, j := range mj { 449 c := j.Code() 450 if c > code { 451 code = c 452 } 453 } 454 return code 455 } 456 457 func (mj MultiJob) Message() string { 458 var buf bytes.Buffer 459 for _, j := range mj { 460 if buf.Len() > 0 { 461 buf.WriteString("\n") 462 } 463 buf.WriteString(j.Message()) 464 } 465 return buf.String() 466 } 467 468 func (mj MultiJob) Summary() (string, error) { 469 c := len(mj) 470 var ok, warn, err int 471 for _, j := range mj { 472 switch j.Code() { 473 case OK: 474 ok++ 475 case WARN: 476 warn++ 477 case ERR: 478 err++ 479 } 480 } 481 if ok == c { 482 m := "all jobs succeeded" 483 if c == 1 { 484 m = "job succeeded" 485 } 486 return m, nil 487 } 488 if ok == 0 { 489 m := "none of the jobs succeeded" 490 if c == 1 { 491 m = "job failed" 492 } 493 return "", errors.New(m) 494 } 495 if err > 0 { 496 return "", fmt.Errorf("%d jobs failed - %d jobs succeeded and %d had warnings", err, ok, warn) 497 } 498 499 return fmt.Sprintf("%d jobs succeeded - there were %d errors and %d warnings", ok, err, warn), nil 500 }