1
4 package errors
5
6
7
8
9
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
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
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
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
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
116 func InvalidArgs(ctx context.Context, publicmessage string, logmessage string, a ...interface{}) error {
117 return Error(ctx, codes.InvalidArgument, publicmessage, logmessage, a...)
118
119 }
120
121
122 func stdText(ctx context.Context) string {
123 user := auth.CurrentUserString(ctx)
124 svc := auth.GetService(ctx)
125
126 caller := "nil"
127 callee, _ := callingFunction()
128
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
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
151 add := &common.Status{ErrorCode: int32(code), ErrorDescription: log}
152 st, err = st.WithDetails(add)
153 if err != nil {
154
155
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
221
222 func ErrorString(err error) string {
223 return shared.ErrorString(err)
224 }
225
226
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