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  }