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  }