forked from juju/juju
/
errors.go
140 lines (120 loc) · 4.1 KB
/
errors.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
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package google
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/juju/errors"
"google.golang.org/api/googleapi"
"github.com/DavinZhang/juju/environs/context"
"github.com/DavinZhang/juju/provider/common"
)
// InvalidConfigValueError indicates that one of the config values failed validation.
type InvalidConfigValueError struct {
errors.Err
// Key is the OS env var corresponding to the field with the bad value.
Key string
// Value is the invalid value.
Value interface{}
}
// IsInvalidConfigValueError returns whether or not the cause of
// the provided error is a *InvalidConfigValueError.
func IsInvalidConfigValueError(err error) bool {
_, ok := errors.Cause(err).(*InvalidConfigValueError)
return ok
}
// NewInvalidConfigValueError returns a new InvalidConfigValueError for the given
// info. If the provided reason is an error then Reason is set to that
// error. Otherwise a non-nil value is treated as a string and Reason is
// set to a non-nil value that wraps it.
func NewInvalidConfigValueError(key, value string, reason error) error {
err := &InvalidConfigValueError{
Err: *errors.Mask(reason).(*errors.Err),
Key: key,
Value: value,
}
err.Err.SetLocation(1)
return err
}
// Cause implements errors.Causer.Cause.
func (err *InvalidConfigValueError) Cause() error {
return err
}
// NewMissingConfigValue returns a new error for a missing config field.
func NewMissingConfigValue(key, field string) error {
return NewInvalidConfigValueError(key, "", errors.New("missing "+field))
}
// Error implements error.
func (err InvalidConfigValueError) Error() string {
return fmt.Sprintf("invalid config value (%s) for %q: %v", err.Value, err.Key, &err.Err)
}
// HandleCredentialError determines if a given error relates to an invalid credential.
// If it is, the credential is invalidated. Original error is returned untouched.
func HandleCredentialError(err error, ctx context.ProviderCallContext) error {
maybeInvalidateCredential(err, ctx)
return err
}
func maybeInvalidateCredential(err error, ctx context.ProviderCallContext) bool {
if ctx == nil {
return false
}
if !HasDenialStatusCode(err) {
return false
}
converted := common.CredentialNotValidf(err, "google cloud denied access")
invalidateErr := ctx.InvalidateCredential(converted.Error())
if invalidateErr != nil {
logger.Warningf("could not invalidate stored google cloud credential on the controller: %v", invalidateErr)
}
return true
}
// HasDenialStatusCode determines if the given error was caused by an invalid credential, i.e. whether it contains a
// response status code that indicates an authentication failure.
func HasDenialStatusCode(err error) bool {
if err == nil {
return false
}
var cause error
switch e := errors.Cause(err).(type) {
case *url.Error:
cause = e
case *googleapi.Error:
cause = e
default:
return false
}
for code, descs := range AuthorisationFailureStatusCodes {
for _, desc := range descs {
if strings.Contains(cause.Error(), fmt.Sprintf(": %v %v", code, desc)) {
return true
}
}
}
return false
}
// AuthorisationFailureStatusCodes contains http status code and
// description that signify authorisation difficulties.
//
// Google does not always use standard HTTP descriptions, which
// is why a single status code can map to multiple descriptions.
var AuthorisationFailureStatusCodes = map[int][]string{
http.StatusUnauthorized: {"Unauthorized"},
http.StatusPaymentRequired: {"Payment Required"},
http.StatusForbidden: {"Forbidden", "Access Not Configured"},
http.StatusProxyAuthRequired: {"Proxy Auth Required"},
// OAuth 2.0 also implements RFC#6749, so we need to cater for specific BadRequest errors.
// https://tools.ietf.org/html/rfc6749#section-5.2
http.StatusBadRequest: {"Bad Request"},
}
// IsNotFound reports if given error is of 'not found' type.
func IsNotFound(err error) bool {
if err == nil {
return false
}
if gerr, ok := errors.Cause(err).(*googleapi.Error); ok {
return gerr.Code == http.StatusNotFound
}
return errors.IsNotFound(errors.Cause(err))
}