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