package gcp import ( "errors" "net/http" "slices" "github.com/go-logr/logr" "google.golang.org/api/googleapi" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) // classify maps a GCP API error onto the provider taxonomy: // 404 → NotFound; 429 and quota-flavored 403s → QuotaExceeded; // 400/401/other 403s → Permanent; 408/5xx and anything unrecognized // (network errors, context cancellation) → Transient, because retrying is // always safer than latching Failed on an error nobody taught this // function to recognize. func classify(err error) error { var gerr *googleapi.Error if !errors.As(err, &gerr) { return provider.ErrTransient } switch { case gerr.Code == http.StatusNotFound: return provider.ErrNotFound case gerr.Code == http.StatusTooManyRequests: return provider.ErrQuotaExceeded case gerr.Code == http.StatusForbidden && hasReason(gerr, "quotaExceeded", "rateLimitExceeded"): return provider.ErrQuotaExceeded case gerr.Code == http.StatusBadRequest, gerr.Code == http.StatusUnauthorized, gerr.Code == http.StatusForbidden: return provider.ErrPermanent default: return provider.ErrTransient } } func (p *Provider) wrapErr(op, id string, err error) error { return provider.Wrap(classify(err), op, p.name, id, err) } // logAPIError records the raw googleapi error shape (HTTP status, reasons) // at V(1) — classify collapses it onto the coarser provider taxonomy, so // this line is the only place the original status survives. func logAPIError(log logr.Logger, op string, err error) { if !log.V(1).Enabled() { return } kv := []any{"op", op, "error", err.Error()} var gerr *googleapi.Error if errors.As(err, &gerr) { reasons := make([]string, 0, len(gerr.Errors)) for _, item := range gerr.Errors { reasons = append(reasons, item.Reason) } kv = append(kv, "httpStatus", gerr.Code, "reasons", reasons) } log.V(1).Info("GCP API call failed", kv...) } func hasReason(gerr *googleapi.Error, reasons ...string) bool { for _, item := range gerr.Errors { if slices.Contains(reasons, item.Reason) { return true } } return false } func isAlreadyExists(err error) bool { var gerr *googleapi.Error return errors.As(err, &gerr) && gerr.Code == http.StatusConflict } func isNotFound(err error) bool { var gerr *googleapi.Error return errors.As(err, &gerr) && gerr.Code == http.StatusNotFound }