github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/gce/google/errors.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package google 5 6 import ( 7 "fmt" 8 "net/http" 9 "net/url" 10 "strings" 11 12 "github.com/juju/errors" 13 "google.golang.org/api/googleapi" 14 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/provider/common" 17 ) 18 19 // InvalidConfigValueError indicates that one of the config values failed validation. 20 type InvalidConfigValueError struct { 21 errors.Err 22 23 // Key is the OS env var corresponding to the field with the bad value. 24 Key string 25 26 // Value is the invalid value. 27 Value interface{} 28 } 29 30 // IsInvalidConfigValueError returns whether or not the cause of 31 // the provided error is a *InvalidConfigValueError. 32 func IsInvalidConfigValueError(err error) bool { 33 _, ok := errors.Cause(err).(*InvalidConfigValueError) 34 return ok 35 } 36 37 // NewInvalidConfigValueError returns a new InvalidConfigValueError for the given 38 // info. If the provided reason is an error then Reason is set to that 39 // error. Otherwise a non-nil value is treated as a string and Reason is 40 // set to a non-nil value that wraps it. 41 func NewInvalidConfigValueError(key, value string, reason error) error { 42 err := &InvalidConfigValueError{ 43 Err: *errors.Mask(reason).(*errors.Err), 44 Key: key, 45 Value: value, 46 } 47 err.Err.SetLocation(1) 48 return err 49 } 50 51 // Cause implements errors.Causer.Cause. 52 func (err *InvalidConfigValueError) Cause() error { 53 return err 54 } 55 56 // NewMissingConfigValue returns a new error for a missing config field. 57 func NewMissingConfigValue(key, field string) error { 58 return NewInvalidConfigValueError(key, "", errors.New("missing "+field)) 59 } 60 61 // Error implements error. 62 func (err InvalidConfigValueError) Error() string { 63 return fmt.Sprintf("invalid config value (%s) for %q: %v", err.Value, err.Key, &err.Err) 64 } 65 66 // HandleCredentialError determines if a given error relates to an invalid credential. 67 // If it is, the credential is invalidated. Original error is returned untouched. 68 func HandleCredentialError(err error, ctx context.ProviderCallContext) error { 69 maybeInvalidateCredential(err, ctx) 70 return err 71 } 72 73 func maybeInvalidateCredential(err error, ctx context.ProviderCallContext) bool { 74 if ctx == nil { 75 return false 76 } 77 if !HasDenialStatusCode(err) { 78 return false 79 } 80 81 converted := fmt.Errorf("google cloud denied access: %w", common.CredentialNotValidError(err)) 82 invalidateErr := ctx.InvalidateCredential(converted.Error()) 83 if invalidateErr != nil { 84 logger.Warningf("could not invalidate stored google cloud credential on the controller: %v", invalidateErr) 85 } 86 return true 87 } 88 89 // HasDenialStatusCode determines if the given error was caused by an invalid credential, i.e. whether it contains a 90 // response status code that indicates an authentication failure. 91 func HasDenialStatusCode(err error) bool { 92 if err == nil { 93 return false 94 } 95 96 var cause error 97 switch e := errors.Cause(err).(type) { 98 case *url.Error: 99 cause = e 100 case *googleapi.Error: 101 cause = e 102 default: 103 return false 104 } 105 106 for code, descs := range AuthorisationFailureStatusCodes { 107 for _, desc := range descs { 108 if strings.Contains(cause.Error(), fmt.Sprintf(": %v %v", code, desc)) { 109 return true 110 } 111 } 112 } 113 return false 114 } 115 116 // AuthorisationFailureStatusCodes contains http status code and 117 // description that signify authorisation difficulties. 118 // 119 // Google does not always use standard HTTP descriptions, which 120 // is why a single status code can map to multiple descriptions. 121 var AuthorisationFailureStatusCodes = map[int][]string{ 122 http.StatusUnauthorized: {"Unauthorized"}, 123 http.StatusPaymentRequired: {"Payment Required"}, 124 http.StatusForbidden: {"Forbidden", "Access Not Configured"}, 125 http.StatusProxyAuthRequired: {"Proxy Auth Required"}, 126 // OAuth 2.0 also implements RFC#6749, so we need to cater for specific BadRequest errors. 127 // https://tools.ietf.org/html/rfc6749#section-5.2 128 http.StatusBadRequest: {"Bad Request"}, 129 } 130 131 // IsNotFound reports if given error is of 'not found' type. 132 func IsNotFound(err error) bool { 133 if err == nil { 134 return false 135 } 136 if gerr, ok := errors.Cause(err).(*googleapi.Error); ok { 137 return gerr.Code == http.StatusNotFound 138 } 139 return errors.IsNotFound(errors.Cause(err)) 140 }