forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
apierror.go
417 lines (342 loc) · 12.8 KB
/
apierror.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package params
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/version/v2"
"gopkg.in/macaroon.v2"
)
var logger = loggo.GetLogger("juju.apiserver.params")
// UpgradeInProgressError signifies an upgrade is in progress.
var UpgradeInProgressError = errors.New(CodeUpgradeInProgress)
// MigrationInProgressError signifies a migration is in progress.
var MigrationInProgressError = errors.New(CodeMigrationInProgress)
// IncompatibleClientError signifies the connecting client is not
// compatible with the controller.
type IncompatibleClientError struct {
ServerVersion version.Number
}
// Error implements error.
func (e *IncompatibleClientError) Error() string {
return fmt.Sprintf("client incompatible with server %v", e.ServerVersion)
}
// AsMap returns the data for the RPC error Info field.
func (e *IncompatibleClientError) AsMap() map[string]interface{} {
return map[string]interface{}{
"server-version": e.ServerVersion,
}
}
// IsIncompatibleClientError returns true if this err is a IncompatibleClientError.
func IsIncompatibleClientError(err error) bool {
_, ok := errors.Cause(err).(*IncompatibleClientError)
return ok
}
// Error is the type of error returned by any call to the state API.
type Error struct {
Message string `json:"message"`
Code string `json:"code"`
Info map[string]interface{} `json:"info,omitempty"`
}
func (e Error) Error() string {
return e.Message
}
func (e Error) ErrorCode() string {
return e.Code
}
// ErrorInfo implements the rpc.ErrorInfoProvider interface which enables
// API error attachments to be returned as part of RPC error responses.
func (e Error) ErrorInfo() map[string]interface{} {
return e.Info
}
// GoString implements fmt.GoStringer. It means that a *Error shows its
// contents correctly when printed with %#v.
func (e Error) GoString() string {
return fmt.Sprintf("¶ms.Error{Message: %q, Code: %q}", e.Message, e.Code)
}
// UnmarshalInfo attempts to unmarshal the information contained in the Info
// field of a RequestError into an AdditionalErrorInfo instance a pointer to
// which is passed via the to argument. The method will return an error if a
// non-pointer arg is provided.
func (e Error) UnmarshalInfo(to interface{}) error {
if reflect.ValueOf(to).Kind() != reflect.Ptr {
return errors.New("UnmarshalInfo expects a pointer as an argument")
}
data, err := json.Marshal(e.Info)
if err != nil {
return errors.Annotate(err, "could not marshal error information")
}
err = json.Unmarshal(data, to)
if err != nil {
return errors.Annotate(err, "could not unmarshal error information to provided target")
}
return nil
}
// DischargeRequiredErrorInfo provides additional macaroon information for
// DischargeRequired errors. Note that although these fields are compatible
// with the same fields in httpbakery.ErrorInfo, the Juju API server does not
// implement endpoints directly compatible with that protocol because the error
// response format varies according to the endpoint.
type DischargeRequiredErrorInfo struct {
// Macaroon may hold a macaroon that, when
// discharged, may allow access to the juju API.
// This field is associated with the ErrDischargeRequired
// error code.
Macaroon *macaroon.Macaroon `json:"macaroon,omitempty"`
// BakeryMacaroon may hold a macaroon that, when
// discharged, may allow access to the juju API.
// This field is associated with the ErrDischargeRequired
// error code.
// This is the macaroon emitted by newer Juju controllers using bakery.v2.
BakeryMacaroon *bakery.Macaroon `json:"bakery-macaroon,omitempty"`
// MacaroonPath holds the URL path to be associated
// with the macaroon. The macaroon is potentially
// valid for all URLs under the given path.
// If it is empty, the macaroon will be associated with
// the original URL from which the error was returned.
MacaroonPath string `json:"macaroon-path,omitempty"`
}
// AsMap encodes the error info as a map that can be attached to an Error.
func (e DischargeRequiredErrorInfo) AsMap() map[string]interface{} {
return serializeToMap(e)
}
// RedirectErrorInfo provides additional information for Redirect errors.
type RedirectErrorInfo struct {
// Servers holds the sets of addresses of the redirected servers.
Servers [][]HostPort `json:"servers"`
// CACert holds the certificate of the remote server.
CACert string `json:"ca-cert"`
// ControllerTag uniquely identifies the controller being redirected to.
ControllerTag string `json:"controller-tag,omitempty"`
// An optional alias for the controller the model migrated to.
ControllerAlias string `json:"controller-alias,omitempty"`
}
// AsMap encodes the error info as a map that can be attached to an Error.
func (e RedirectErrorInfo) AsMap() map[string]interface{} {
return serializeToMap(e)
}
// serializeToMap is a convenience function for marshaling v into a
// map[string]interface{}. It works by marshalling v into json and then
// unmarshaling back to a map.
func serializeToMap(v interface{}) map[string]interface{} {
data, err := json.Marshal(v)
if err != nil {
logger.Criticalf("serializeToMap: marshal to json failed: %v", err)
return nil
}
var asMap map[string]interface{}
err = json.Unmarshal(data, &asMap)
if err != nil {
logger.Criticalf("serializeToMap: unmarshal to map failed: %v", err)
return nil
}
return asMap
}
// The Code constants hold error codes for some kinds of error.
const (
CodeNotFound = "not found"
CodeUserNotFound = "user not found"
CodeModelNotFound = "model not found"
CodeUnauthorized = "unauthorized access"
CodeLoginExpired = "login expired"
CodeNoCreds = "no credentials provided"
CodeCannotEnterScope = "cannot enter scope"
CodeCannotEnterScopeYet = "cannot enter scope yet"
CodeExcessiveContention = "excessive contention"
CodeUnitHasSubordinates = "unit has subordinates"
CodeNotAssigned = "not assigned"
CodeStopped = "stopped"
CodeDead = "dead"
CodeHasAssignedUnits = "machine has assigned units"
CodeHasHostedModels = "controller has hosted models"
CodeHasPersistentStorage = "controller/model has persistent storage"
CodeModelNotEmpty = "model not empty"
CodeMachineHasAttachedStorage = "machine has attached storage"
CodeMachineHasContainers = "machine is hosting containers"
CodeStorageAttached = "storage is attached"
CodeNotProvisioned = "not provisioned"
CodeNoAddressSet = "no address set"
CodeTryAgain = "try again"
CodeNotImplemented = "not implemented" // asserted to match rpc.codeNotImplemented in rpc/rpc_test.go
CodeAlreadyExists = "already exists"
CodeUpgradeInProgress = "upgrade in progress"
CodeMigrationInProgress = "model migration in progress"
CodeIncompatibleClient = "incompatible client"
CodeActionNotAvailable = "action no longer available"
CodeOperationBlocked = "operation is blocked"
CodeLeadershipClaimDenied = "leadership claim denied"
CodeLeaseClaimDenied = "lease claim denied"
CodeNotSupported = "not supported"
CodeBadRequest = "bad request"
CodeMethodNotAllowed = "method not allowed"
CodeForbidden = "forbidden"
CodeDischargeRequired = "macaroon discharge required"
CodeRedirect = "redirection required"
CodeIncompatibleSeries = "incompatible series"
CodeCloudRegionRequired = "cloud region required"
CodeIncompatibleClouds = "incompatible clouds"
CodeQuotaLimitExceeded = "quota limit exceeded"
CodeNotYetAvailable = "not yet available; try again later"
CodeNotLeader = "not leader"
CodeDeadlineExceeded = "deadline exceeded"
CodeLeaseError = "lease error"
)
// ErrCode returns the error code associated with
// the given error, or the empty string if there
// is none.
func ErrCode(err error) string {
type ErrorCoder interface {
ErrorCode() string
}
switch err := errors.Cause(err).(type) {
case ErrorCoder:
return err.ErrorCode()
default:
return ""
}
}
func IsCodeActionNotAvailable(err error) bool {
return ErrCode(err) == CodeActionNotAvailable
}
func IsCodeNotFound(err error) bool {
return ErrCode(err) == CodeNotFound
}
func IsCodeUserNotFound(err error) bool {
return ErrCode(err) == CodeUserNotFound
}
func IsCodeModelNotFound(err error) bool {
return ErrCode(err) == CodeModelNotFound
}
func IsCodeUnauthorized(err error) bool {
return ErrCode(err) == CodeUnauthorized
}
func IsCodeNoCreds(err error) bool {
// When we receive this error from an rpc call, rpc.RequestError
// is populated with a CodeUnauthorized code and a message that
// is formatted as "$CodeNoCreds ($CodeUnauthorized)".
ec := ErrCode(err)
return ec == CodeNoCreds || (ec == CodeUnauthorized && strings.HasPrefix(errors.Cause(err).Error(), CodeNoCreds))
}
func IsCodeLoginExpired(err error) bool {
return ErrCode(err) == CodeLoginExpired
}
func IsCodeNotYetAvailable(err error) bool {
return ErrCode(err) == CodeNotYetAvailable
}
// IsCodeNotFoundOrCodeUnauthorized is used in API clients which,
// pre-API, used errors.IsNotFound; this is because an API client is
// not necessarily privileged to know about the existence or otherwise
// of a particular entity, and the server may hence convert NotFound
// to Unauthorized at its discretion.
func IsCodeNotFoundOrCodeUnauthorized(err error) bool {
return IsCodeNotFound(err) || IsCodeUnauthorized(err)
}
func IsCodeCannotEnterScope(err error) bool {
return ErrCode(err) == CodeCannotEnterScope
}
func IsCodeCannotEnterScopeYet(err error) bool {
return ErrCode(err) == CodeCannotEnterScopeYet
}
func IsCodeExcessiveContention(err error) bool {
return ErrCode(err) == CodeExcessiveContention
}
func IsCodeUnitHasSubordinates(err error) bool {
return ErrCode(err) == CodeUnitHasSubordinates
}
func IsCodeNotAssigned(err error) bool {
return ErrCode(err) == CodeNotAssigned
}
func IsCodeStopped(err error) bool {
return ErrCode(err) == CodeStopped
}
func IsCodeDead(err error) bool {
return ErrCode(err) == CodeDead
}
func IsCodeHasAssignedUnits(err error) bool {
return ErrCode(err) == CodeHasAssignedUnits
}
func IsCodeHasHostedModels(err error) bool {
return ErrCode(err) == CodeHasHostedModels
}
func IsCodeHasPersistentStorage(err error) bool {
return ErrCode(err) == CodeHasPersistentStorage
}
func IsCodeModelNotEmpty(err error) bool {
return ErrCode(err) == CodeModelNotEmpty
}
func IsCodeMachineHasAttachedStorage(err error) bool {
return ErrCode(err) == CodeMachineHasAttachedStorage
}
func IsCodeMachineHasContainers(err error) bool {
return ErrCode(err) == CodeMachineHasContainers
}
func IsCodeStorageAttached(err error) bool {
return ErrCode(err) == CodeStorageAttached
}
func IsCodeNotProvisioned(err error) bool {
return ErrCode(err) == CodeNotProvisioned
}
func IsCodeNoAddressSet(err error) bool {
return ErrCode(err) == CodeNoAddressSet
}
func IsCodeTryAgain(err error) bool {
return ErrCode(err) == CodeTryAgain
}
func IsCodeNotImplemented(err error) bool {
return ErrCode(err) == CodeNotImplemented
}
func IsCodeAlreadyExists(err error) bool {
return ErrCode(err) == CodeAlreadyExists
}
func IsCodeUpgradeInProgress(err error) bool {
return ErrCode(err) == CodeUpgradeInProgress
}
func IsCodeOperationBlocked(err error) bool {
return ErrCode(err) == CodeOperationBlocked
}
func IsCodeLeadershipClaimDenied(err error) bool {
return ErrCode(err) == CodeLeadershipClaimDenied
}
func IsCodeLeaseClaimDenied(err error) bool {
return ErrCode(err) == CodeLeaseClaimDenied
}
func IsCodeNotSupported(err error) bool {
return ErrCode(err) == CodeNotSupported
}
func IsBadRequest(err error) bool {
return ErrCode(err) == CodeBadRequest
}
func IsMethodNotAllowed(err error) bool {
return ErrCode(err) == CodeMethodNotAllowed
}
func IsRedirect(err error) bool {
return ErrCode(err) == CodeRedirect
}
func IsCodeIncompatibleSeries(err error) bool {
return ErrCode(err) == CodeIncompatibleSeries
}
func IsCodeForbidden(err error) bool {
return ErrCode(err) == CodeForbidden
}
func IsCodeCloudRegionRequired(err error) bool {
return ErrCode(err) == CodeCloudRegionRequired
}
// IsCodeQuotaLimitExceeded returns true if err includes a QuotaLimitExceeded
// error code.
func IsCodeQuotaLimitExceeded(err error) bool {
return ErrCode(err) == CodeQuotaLimitExceeded
}
func IsCodeNotLeader(err error) bool {
return ErrCode(err) == CodeNotLeader
}
func IsCodeDeadlineExceeded(err error) bool {
return ErrCode(err) == CodeDeadlineExceeded
}
func IsLeaseError(err error) bool {
return ErrCode(err) == CodeLeaseError
}