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  }