github.com/henvic/wedeploycli@v1.7.6-0.20200319005353-3630f582f284/errorhandler/errorhandler.go (about) 1 /* 2 Package errorhandler provides a error handling system to be used as 3 root.Execute() error handler or on watches. It should not be used somewhere else. 4 */ 5 package errorhandler 6 7 import ( 8 "fmt" 9 "net/url" 10 "os" 11 "reflect" 12 "runtime" 13 "strings" 14 "time" 15 16 "github.com/hashicorp/errwrap" 17 "github.com/henvic/wedeploycli/apihelper" 18 "github.com/henvic/wedeploycli/color" 19 "github.com/henvic/wedeploycli/defaults" 20 "github.com/henvic/wedeploycli/templates" 21 "github.com/henvic/wedeploycli/verbose" 22 ) 23 24 const panicTemplate = `An unrecoverable error has occurred. 25 Please report this error at 26 https://github.com/wedeploy/cli/issues/ 27 28 %s 29 Time: %s 30 %s` 31 32 // CommandName for the local message repository 33 var CommandName string 34 35 var afterError func() 36 37 // SetAfterError defines a function to run after global error 38 func SetAfterError(f func()) { 39 afterError = f 40 } 41 42 // RunAfterError runs code after a global error, just before exiting 43 func RunAfterError() { 44 if afterError != nil { 45 afterError() 46 } 47 } 48 49 // Handle error to a more friendly format 50 func Handle(err error) error { 51 if err == nil { 52 return nil 53 } 54 55 return (&handler{ 56 err: err, 57 }).handle() 58 } 59 60 type handler struct { 61 err error 62 } 63 64 type messages map[string]string 65 66 func extractParentCommand(cmd string) string { 67 var splitCmd = strings.Split(cmd, " ") 68 return strings.Join(splitCmd[:len(splitCmd)-1], " ") 69 } 70 71 type data struct { 72 Err error 73 Context map[string]interface{} 74 } 75 76 // tryGetPersonalizedMessage tries to get a human-friendly error message from the 77 // command / local error message lists falling back to the parent command 78 // and at last instance to the global 79 func tryGetPersonalizedMessage(cmd, reason string, d data) (string, bool) { 80 local := cmd 81 getMessage: 82 if haystack, ok := errorReasonCommandMessageOverrides[local]; ok { 83 if msg, has := haystack[reason]; has { 84 return msg, true 85 } 86 } 87 88 if local = extractParentCommand(local); local != "" { 89 goto getMessage 90 } 91 92 msg, ok := errorReasonMessage[reason] 93 94 if !ok { 95 return msg, ok 96 } 97 98 personalizedMsg, err := templates.Execute(msg, d) 99 100 if err != nil { 101 verbose.Debug(errwrap.Wrapf("error getting personalized message: {{err}}", err)) 102 return msg, ok 103 } 104 105 return personalizedMsg, ok 106 } 107 108 func (h *handler) handle() error { 109 if af := errwrap.GetType(h.err, apihelper.APIFault{}); af != nil { 110 return h.handleAPIFaultError() 111 } 112 113 switch nerr, ok := h.err.(*url.Error); { 114 case !ok: 115 return h.err 116 case nerr.Timeout(): 117 return errwrap.Wrapf("network connection timed out:\n{{err}}", h.err) 118 default: 119 return errwrap.Wrapf("network connection error:\n{{err}}", h.err) 120 } 121 } 122 123 func (h *handler) handleAPIFaultError() error { 124 var af, ok = errwrap.GetType(h.err, apihelper.APIFault{}).(apihelper.APIFault) 125 126 if !ok { 127 return h.err 128 } 129 130 var msgs []string 131 // we want to fallback to the default error if no friendly messages are found 132 var anyFriendly bool 133 134 for _, e := range af.Errors { 135 d := data{ 136 Err: h.err, 137 Context: e.Context, 138 } 139 140 rtm, ok := tryGetPersonalizedMessage(CommandName, e.Reason, d) 141 142 if ok { 143 anyFriendly = true 144 msgs = append(msgs, rtm) 145 } else { 146 msgs = append(msgs, e.Reason+": "+e.Context.Message()) 147 } 148 149 } 150 151 if !anyFriendly { 152 return h.err 153 } 154 155 var l = strings.Join(msgs, "\n") 156 157 var msg = strings.Replace(h.err.Error(), af.Error(), l, -1) 158 159 return errwrap.Wrapf(msg, h.err) 160 } 161 162 // GetTypes get a list of error types separated by ":" 163 func GetTypes(err error) string { 164 var types []string 165 166 errwrap.Walk(err, func(err error) { 167 r := reflect.TypeOf(err) 168 types = append(types, r.String()) 169 }) 170 171 return strings.Join(types, ":") 172 } 173 174 // Info prints useful system information for debugging 175 func Info() { 176 var version = fmt.Sprintf("Version: %s %s/%s (runtime: %s)", 177 defaults.Version, 178 runtime.GOOS, 179 runtime.GOARCH, 180 runtime.Version()) 181 182 if defaults.Build != "" { 183 version += "\nbuild:" + defaults.Build 184 } 185 186 _, _ = fmt.Fprintln(os.Stderr, color.Format(color.FgRed, panicTemplate, 187 version, 188 time.Now().Format(time.RubyDate), systemInfo())) 189 } 190 191 func systemInfo() string { 192 var m runtime.MemStats 193 runtime.ReadMemStats(&m) 194 195 return fmt.Sprintf(`goroutines: %v | cgo calls: %v 196 CPUs: %v | Pointer lookups: %v 197 `, runtime.NumGoroutine(), runtime.NumCgoCall(), runtime.NumCPU(), m.Lookups) 198 }