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  }