github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 14 "github.com/juju/juju/environs/context" 15 ) 16 17 // InvalidConfigValueError indicates that one of the config values failed validation. 18 type InvalidConfigValueError struct { 19 errors.Err 20 21 // Key is the OS env var corresponding to the field with the bad value. 22 Key string 23 24 // Value is the invalid value. 25 Value interface{} 26 } 27 28 // IsInvalidConfigValueError returns whether or not the cause of 29 // the provided error is a *InvalidConfigValueError. 30 func IsInvalidConfigValueError(err error) bool { 31 _, ok := errors.Cause(err).(*InvalidConfigValueError) 32 return ok 33 } 34 35 // NewInvalidConfigValueError returns a new InvalidConfigValueError for the given 36 // info. If the provided reason is an error then Reason is set to that 37 // error. Otherwise a non-nil value is treated as a string and Reason is 38 // set to a non-nil value that wraps it. 39 func NewInvalidConfigValueError(key, value string, reason error) error { 40 err := &InvalidConfigValueError{ 41 Err: *errors.Mask(reason).(*errors.Err), 42 Key: key, 43 Value: value, 44 } 45 err.Err.SetLocation(1) 46 return err 47 } 48 49 // Cause implements errors.Causer.Cause. 50 func (err *InvalidConfigValueError) Cause() error { 51 return err 52 } 53 54 // NewMissingConfigValue returns a new error for a missing config field. 55 func NewMissingConfigValue(key, field string) error { 56 return NewInvalidConfigValueError(key, "", errors.New("missing "+field)) 57 } 58 59 // Error implements error. 60 func (err InvalidConfigValueError) Error() string { 61 return fmt.Sprintf("invalid config value (%s) for %q: %v", err.Value, err.Key, &err.Err) 62 } 63 64 // HandleCredentialError determines if a given error relates to an invalid credential. 65 // If it is, the credential is invalidated. Original error is returned untouched. 66 func HandleCredentialError(err error, ctx context.ProviderCallContext) error { 67 maybeInvalidateCredential(err, ctx) 68 return err 69 } 70 71 func maybeInvalidateCredential(err error, ctx context.ProviderCallContext) bool { 72 if ctx == nil { 73 return false 74 } 75 if !HasDenialStatusCode(err) { 76 return false 77 } 78 79 invalidateErr := ctx.InvalidateCredential("google cloud denied access") 80 if invalidateErr != nil { 81 logger.Warningf("could not invalidate stored google cloud credential on the controller: %v", invalidateErr) 82 } 83 return true 84 } 85 86 // HasDenialStatusCode determines if the given error was caused by an invalid credential, i.e. whether it contains a 87 // response status code that indicates an authentication failure. 88 func HasDenialStatusCode(err error) bool { 89 if err == nil { 90 return false 91 } 92 93 // http/url.Error is constructed with status code in mind and, at the time of writing for go-1.10, 94 // contains response status code and description in error.Error. 95 // We have to examine the error message to determine whether the error is related to authentication failure. 96 if cause, ok := errors.Cause(err).(*url.Error); ok { 97 for code, desc := range AuthorisationFailureStatusCodes { 98 if strings.Contains(cause.Error(), fmt.Sprintf(": %v %v", code, desc)) { 99 return true 100 } 101 } 102 } 103 return false 104 105 } 106 107 // AuthorisationFailureStatusCodes contains http status code nad description that signify authorisation difficulties. 108 var AuthorisationFailureStatusCodes = map[int]string{ 109 http.StatusUnauthorized: "Unauthorized", 110 http.StatusPaymentRequired: "Payment Required", 111 http.StatusForbidden: "Forbidden", 112 http.StatusProxyAuthRequired: "Proxy Auth Required", 113 // OAuth 2.0 also implements RFC#6749, so we need to cater for specific BadRequest errors. 114 // https://tools.ietf.org/html/rfc6749#section-5.2 115 http.StatusBadRequest: "Bad Request", 116 }