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("&params.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  }