...

Source file src/golang.conradwood.net/go-easyops/errors/errors.go

Documentation: golang.conradwood.net/go-easyops/errors

     1  /*
     2  wrappers around errors to include more information than standard fmt.Errorf through use of context information
     3  */
     4  package errors
     5  
     6  // package errors
     7  // grpc Servers should *only* return errors created by this package.
     8  // so instead of fmt.Errorf() or status.Error use
     9  // errors.Error() (this package)
    10  import (
    11  	"context"
    12  	"fmt"
    13  
    14  	"golang.conradwood.net/apis/common"
    15  	fw "golang.conradwood.net/apis/framework"
    16  	"golang.conradwood.net/go-easyops/auth"
    17  	"golang.conradwood.net/go-easyops/errors/shared"
    18  
    19  	//	"golang.conradwood.net/go-easyops/utils"
    20  	"flag"
    21  
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  )
    25  
    26  var (
    27  	encapsulate_error = flag.Bool("ge_encapsulate_errors", false, "if true encapsulate errors with stacktrace")
    28  	// mapping as per https://cloud.google.com/apis/design/errors
    29  	grpcToHTTPMap = map[codes.Code]*HTTPError{
    30  		codes.OK:                 {200, "ok", "", ""},
    31  		codes.Unknown:            {500, "unknown method", "", ""},
    32  		codes.InvalidArgument:    {400, "invalid argument", "", ""},
    33  		codes.DeadlineExceeded:   {504, "deadline exceeded", "", ""},
    34  		codes.NotFound:           {404, "not found", "", ""},
    35  		codes.AlreadyExists:      {409, "resource already exists", "", ""},
    36  		codes.PermissionDenied:   {403, "insufficient permission", "", ""},
    37  		codes.ResourceExhausted:  {429, "out of resource quota", "", ""},
    38  		codes.FailedPrecondition: {400, "not possible in current system state", "", ""},
    39  		codes.Aborted:            {409, "concurrency conflict", "", ""},
    40  		codes.OutOfRange:         {400, "invalid range specified", "", ""},
    41  		codes.Unimplemented:      {501, "method not implemented", "", ""},
    42  		codes.Internal:           {500, "internal server error", "", ""},
    43  		codes.Unavailable:        {503, "service unavailable", "", ""},
    44  		codes.DataLoss:           {500, "internal server error", "", ""},
    45  		codes.Unauthenticated:    {401, "missing, invalid, or expired authentication", "", ""},
    46  	}
    47  )
    48  
    49  type HTTPError struct {
    50  	ErrorCode           int
    51  	ErrorString         string
    52  	ExtendedErrorString string
    53  	ErrorMessage        string
    54  }
    55  
    56  // error if context is not root user or one of the services listed
    57  func NeedServiceOrRoot(ctx context.Context, serviceids []string) error {
    58  	err := NeedsRoot(ctx)
    59  	if err == nil {
    60  		return nil
    61  	}
    62  	u := auth.GetUser(ctx)
    63  	svc := auth.GetService(ctx)
    64  	if svc == nil {
    65  		if u == nil {
    66  			return Unauthenticated(ctx, "goeasyops found no user and no service but NeedServiceOrRoot")
    67  		} else {
    68  			return AccessDenied(ctx, "not allowed")
    69  		}
    70  	}
    71  	for _, svid := range serviceids {
    72  		if svid == svc.ID {
    73  			return nil
    74  		}
    75  	}
    76  	if u == nil {
    77  		return Unauthenticated(ctx, "goeasyops found no user but NeedServiceOrRoot")
    78  	} else {
    79  		return AccessDenied(ctx, "not allowed")
    80  	}
    81  
    82  }
    83  
    84  // function call requires "root" privileges. returns error if user is non-root
    85  func NeedsRoot(ctx context.Context) error {
    86  	u := auth.CurrentUserString(ctx)
    87  	if auth.IsRootUser(auth.GetUser(ctx)) {
    88  		return nil
    89  	}
    90  	return Error(ctx, codes.PermissionDenied, "access denied", "this function requires root privileges (which %s does not have)", u)
    91  }
    92  
    93  func NotImplemented(ctx context.Context, method string) error {
    94  	return Error(ctx, codes.Unimplemented, "functionality is not implemented", "function %s not implemented", method)
    95  }
    96  func Unavailable(ctx context.Context, method string) error {
    97  	return Error(ctx, codes.Unavailable, "currently unavailable", "this RPC or data is currently unavailable (%s)", method)
    98  }
    99  func FailedPrecondition(ctx context.Context, logmessage string, a ...interface{}) error {
   100  	return Error(ctx, codes.FailedPrecondition, "state mismatch", logmessage, a...)
   101  }
   102  func AccessDenied(ctx context.Context, logmessage string, a ...interface{}) error {
   103  	return Error(ctx, codes.PermissionDenied, "access denied", logmessage, a...)
   104  }
   105  func NotFound(ctx context.Context, logmessage string, a ...interface{}) error {
   106  	return Error(ctx, codes.NotFound, "not found", logmessage, a...)
   107  }
   108  func Unauthenticated(ctx context.Context, logmessage string, a ...interface{}) error {
   109  	return Error(ctx, codes.Unauthenticated, "access denied", logmessage, a...)
   110  }
   111  func AlreadyExists(ctx context.Context, logmessage string, a ...interface{}) error {
   112  	return Error(ctx, codes.AlreadyExists, "already exists", logmessage, a...)
   113  }
   114  
   115  // shortcut: we write this so often: user submitted args that aren't valid
   116  func InvalidArgs(ctx context.Context, publicmessage string, logmessage string, a ...interface{}) error {
   117  	return Error(ctx, codes.InvalidArgument, publicmessage, logmessage, a...)
   118  	//	return Error(ctx, codes.FailedPrecondition, publicmessage, logmessage, a...)
   119  }
   120  
   121  // include caller/callee information in logmessage
   122  func stdText(ctx context.Context) string {
   123  	user := auth.CurrentUserString(ctx)
   124  	svc := auth.GetService(ctx)
   125  	//	ls := gctx.GetLocalState(ctx)
   126  	caller := "nil"
   127  	callee, _ := callingFunction()
   128  	//	callee = ls.Info()
   129  	if svc == nil {
   130  		caller = fmt.Sprintf("[noservice]")
   131  	} else {
   132  		caller = fmt.Sprintf("(#%s %s)", svc.ID, svc.Email)
   133  	}
   134  	res := fmt.Sprintf("[ %s called %s as user=%s", caller, callee, user)
   135  	if svc == nil {
   136  		res = res + ", noservice"
   137  	} else {
   138  		res = res + ", service=" + svc.ID + " (" + svc.Email + ")"
   139  	}
   140  	res = res + " ]"
   141  	return res
   142  }
   143  
   144  // really returns a status.Status
   145  func Error(ctx context.Context, code codes.Code, publicmessage string, logmessage string, a ...interface{}) error {
   146  	var err error
   147  	logmessage = fmt.Sprintf("%s \"%s\"", stdText(ctx), logmessage)
   148  	log := fmt.Sprintf(logmessage, a...)
   149  	st := status.New(code, publicmessage)
   150  	// encapsulate "status" with logmessage
   151  	add := &common.Status{ErrorCode: int32(code), ErrorDescription: log}
   152  	st, err = st.WithDetails(add)
   153  	if err != nil {
   154  		// this is bad. we can't create an error to reflect the error
   155  		// in case of double-faults there isn't really any other option than to log and exit
   156  		panic(fmt.Sprintf("Double fault, error in error handler whilst creating error for code=%d, publicmessage=%s, logmessage=%s: %s", code, publicmessage, log, err))
   157  	}
   158  	if *encapsulate_error {
   159  		_, cf := callingFunction()
   160  		me := shared.NewMyError(st.Err(), cf)
   161  		return me
   162  	}
   163  	return st.Err()
   164  }
   165  func ToHTTPCode(err error) *HTTPError {
   166  	st := status.Convert(err)
   167  	code := st.Code()
   168  	he, f := grpcToHTTPMap[code]
   169  	if !f {
   170  		he = &HTTPError{ErrorCode: 500,
   171  			ErrorString:         "Unspecified error",
   172  			ExtendedErrorString: fmt.Sprintf("GRPC Error %d", code),
   173  			ErrorMessage:        "Unspecified error",
   174  		}
   175  	}
   176  	return he
   177  
   178  }
   179  
   180  type GEError struct {
   181  	details []*GEEntry
   182  	code    codes.Code
   183  }
   184  type GEEntry struct {
   185  	txt string
   186  	fmd *fw.FrameworkMessageDetail
   187  }
   188  
   189  func (g *GEError) MultilineError() string {
   190  	res := fmt.Sprintf("Errorcode: %v\n", g.code)
   191  	for _, d := range g.details {
   192  		if d.txt != "" {
   193  			res = res + d.txt + "\n"
   194  			continue
   195  		}
   196  		for _, ct := range d.fmd.CallTraces {
   197  			res = res + ct.Method + ":" + ct.Message + "\n"
   198  		}
   199  	}
   200  	return res
   201  }
   202  func UnmarshalError(err error) *GEError {
   203  	res := &GEError{}
   204  	st := status.Convert(err)
   205  	res.code = st.Code()
   206  	for _, a := range st.Details() {
   207  		fmd, ok := a.(*fw.FrameworkMessageDetail)
   208  		if !ok {
   209  			s := fmt.Sprintf("\"%v\" ", a)
   210  			res.details = append(res.details, &GEEntry{txt: s})
   211  			continue
   212  		}
   213  		res.details = append(res.details, &GEEntry{fmd: fmd})
   214  
   215  	}
   216  
   217  	return res
   218  }
   219  
   220  // extracts the PRIVATE and possibly SENSITIVE debug error message from a string
   221  
   222  func ErrorString(err error) string {
   223  	return shared.ErrorString(err)
   224  }
   225  
   226  // create new error, but include "[file: xxx.go:13]"
   227  func Errorf(format string, args ...interface{}) error {
   228  	_, st := callingFunction()
   229  	err := fmt.Errorf(format, args...)
   230  	me := shared.NewMyError(err, st)
   231  	return me
   232  }
   233  
   234  func Wrap(err error) error {
   235  	if err == nil {
   236  		return nil
   237  	}
   238  	_, st := callingFunction()
   239  	we := shared.NewWrappedError(err, st)
   240  	return we
   241  }
   242  func Wrapf(err error, format string, args ...interface{}) error {
   243  	if err == nil {
   244  		return nil
   245  	}
   246  	_, st := callingFunction()
   247  	we := shared.NewWrappedErrorWithString(err, st, fmt.Sprintf(format, args...))
   248  	return we
   249  }
   250  func ErrorStringWithStackTrace(err error) string {
   251  	return shared.ErrorStringWithStackTrace(err)
   252  }
   253  func ShortMessage(err error) string {
   254  	return shared.ShortMessage(err)
   255  }
   256  

View as plain text