github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachprod/errors/errors.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package errors 12 13 import ( 14 "fmt" 15 "os/exec" 16 17 "github.com/cockroachdb/errors" 18 ) 19 20 // Error is an interface for error types used by the main.wrap() function 21 // to output correctly classified log messages and exit codes. 22 type Error interface { 23 error 24 25 // The exit code for the error when exiting roachprod. 26 ExitCode() int 27 } 28 29 // Exit codes for the errors 30 const ( 31 cmdExitCode = 20 32 cockroachExitCode = 30 33 sshExitCode = 10 34 unclassifiedExitCode = 1 35 ) 36 37 // Cmd wraps errors that result from a non-cockroach command run against 38 // the cluster. 39 // 40 // For errors coming from a cockroach command, use Cockroach. 41 type Cmd struct { 42 Err error 43 } 44 45 func (e Cmd) Error() string { 46 return fmt.Sprintf("COMMAND_PROBLEM: %s", e.Err.Error()) 47 } 48 49 // ExitCode gives the process exit code to return for non-cockroach command 50 // errors. 51 func (e Cmd) ExitCode() int { 52 return cmdExitCode 53 } 54 55 // Format passes formatting responsibilities to cockroachdb/errors 56 func (e Cmd) Format(s fmt.State, verb rune) { 57 errors.FormatError(e, s, verb) 58 } 59 60 // Unwrap the wrapped the non-cockroach command error. 61 func (e Cmd) Unwrap() error { 62 return e.Err 63 } 64 65 // Cockroach wraps errors that result from a cockroach command run against the cluster. 66 // 67 // For non-cockroach commands, use Cmd. 68 type Cockroach struct { 69 Err error 70 } 71 72 func (e Cockroach) Error() string { 73 return fmt.Sprintf("DEAD_ROACH_PROBLEM: %s", e.Err.Error()) 74 } 75 76 // ExitCode gives the process exit code to return for cockroach errors. 77 func (e Cockroach) ExitCode() int { 78 return cockroachExitCode 79 } 80 81 // Format passes formatting responsibilities to cockroachdb/errors 82 func (e Cockroach) Format(s fmt.State, verb rune) { 83 errors.FormatError(e, s, verb) 84 } 85 86 // Unwrap the wrapped cockroach error. 87 func (e Cockroach) Unwrap() error { 88 return e.Err 89 } 90 91 // SSH wraps ssh-specific errors from connections to remote hosts. 92 type SSH struct { 93 Err error 94 } 95 96 func (e SSH) Error() string { 97 return fmt.Sprintf("SSH_PROBLEM: %s", e.Err.Error()) 98 } 99 100 // ExitCode gives the process exit code to return for SSH errors. 101 func (e SSH) ExitCode() int { 102 return sshExitCode 103 } 104 105 // Format passes formatting responsibilities to cockroachdb/errors 106 func (e SSH) Format(s fmt.State, verb rune) { 107 errors.FormatError(e, s, verb) 108 } 109 110 // Unwrap the wrapped SSH error. 111 func (e SSH) Unwrap() error { 112 return e.Err 113 } 114 115 // Unclassified wraps roachprod and unclassified errors. 116 type Unclassified struct { 117 Err error 118 } 119 120 func (e Unclassified) Error() string { 121 return fmt.Sprintf("UNCLASSIFIED_PROBLEM: %s", e.Err.Error()) 122 } 123 124 // ExitCode gives the process exit code to return for unclassified errors. 125 func (e Unclassified) ExitCode() int { 126 return unclassifiedExitCode 127 } 128 129 // Format passes formatting responsibilities to cockroachdb/errors 130 func (e Unclassified) Format(s fmt.State, verb rune) { 131 errors.FormatError(e, s, verb) 132 } 133 134 // Unwrap the wrapped unclassified error. 135 func (e Unclassified) Unwrap() error { 136 return e.Err 137 } 138 139 // ClassifyCmdError classifies an error received while executing a 140 // non-cockroach command remotely over an ssh connection to the right Error 141 // type. 142 func ClassifyCmdError(err error) Error { 143 if err == nil { 144 return nil 145 } 146 147 if exitErr, ok := asExitError(err); ok { 148 if exitErr.ExitCode() == 255 { 149 return SSH{err} 150 } 151 return Cmd{err} 152 } 153 154 return Unclassified{err} 155 } 156 157 // ClassifyCockroachError classifies an error received while executing a 158 // cockroach command remotely over an ssh connection to the right Error type. 159 func ClassifyCockroachError(err error) Error { 160 if err == nil { 161 return nil 162 } 163 164 if exitErr, ok := asExitError(err); ok { 165 if exitErr.ExitCode() == 255 { 166 return SSH{err} 167 } 168 return Cockroach{err} 169 } 170 171 return Unclassified{err} 172 } 173 174 // Extract the an ExitError from err's error tree or (nil, false) if none exists. 175 func asExitError(err error) (*exec.ExitError, bool) { 176 var exitErr *exec.ExitError 177 if errors.As(err, &exitErr) { 178 return exitErr, true 179 } 180 return nil, false 181 } 182 183 // AsError extracts the Error from err's error tree or (nil, false) if none exists. 184 func AsError(err error) (Error, bool) { 185 var e Error 186 if errors.As(err, &e) { 187 return e, true 188 } 189 return nil, false 190 } 191 192 // SelectPriorityError selects an error from the list in this priority order: 193 // 194 // - the Error with the highest exit code 195 // - one of the `error`s 196 // - nil 197 func SelectPriorityError(errors []error) error { 198 var result Error 199 for _, err := range errors { 200 if err == nil { 201 continue 202 } 203 204 rpErr, _ := AsError(err) 205 if result == nil { 206 result = rpErr 207 continue 208 } 209 210 if rpErr.ExitCode() > result.ExitCode() { 211 result = rpErr 212 } 213 } 214 215 if result != nil { 216 return result 217 } 218 219 for _, err := range errors { 220 if err != nil { 221 return err 222 } 223 } 224 return nil 225 }