func HasStatusForbiddenOrIgnoredError(err error) bool {
	if err == nil {
		return false
	}
	if strings.Contains(err.Error(), fmt.Sprintf("HTTPStatusCode: %d", http.StatusNotFound)) {
		return true
	}
	if strings.Contains(err.Error(), fmt.Sprintf("HTTPStatusCode: %d", http.StatusForbidden)) {
		return true
	}
	return false
}
HasStatusForbiddenOrIgnoredError return true if the given error code is part of the error message
This should only be used when trying to delete resources
package retry
import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"
	"k8s.io/klog/v2"
)
const (
	RetryAfterHeaderKey = "Retry-After"
)
var (
	now = time.Now
	StatusCodesForRetry = []int{
		http.StatusRequestTimeout,       
		http.StatusInternalServerError,  
		http.StatusBadGateway,           
		http.StatusServiceUnavailable,   
		http.StatusGatewayTimeout,       
	}
)
type Error struct {
	Retriable bool
	HTTPStatusCode int
	RetryAfter time.Time
	RawError error
}
func (err *Error) Error() error {
	if err == nil {
		return nil
	}
	retryAfterSeconds := 0
	curTime := now()
	if err.RetryAfter.After(curTime) {
		retryAfterSeconds = int(err.RetryAfter.Sub(curTime) / time.Second)
	}
	return fmt.Errorf("Retriable: %v, RetryAfter: %ds, HTTPStatusCode: %d, RawError: %w",
		err.Retriable, retryAfterSeconds, err.HTTPStatusCode, err.RawError)
}
func (err *Error) IsThrottled() bool {
	if err == nil {
		return false
	}
	return err.HTTPStatusCode == http.StatusTooManyRequests || err.RetryAfter.After(now())
}
func (err *Error) IsNotFound() bool {
	if err == nil {
		return false
	}
	return err.HTTPStatusCode == http.StatusNotFound
}
func NewError(retriable bool, err error) *Error {
	return &Error{
		Retriable: retriable,
		RawError:  err,
	}
}
func GetRetriableError(err error) *Error {
	return &Error{
		Retriable: true,
		RawError:  err,
	}
}
func GetRateLimitError(isWrite bool, opName string) *Error {
	opType := "read"
	if isWrite {
		opType = "write"
	}
	return GetRetriableError(fmt.Errorf("azure cloud provider rate limited(%s) for operation %q", opType, opName))
}
func GetThrottlingError(operation, reason string, retryAfter time.Time) *Error {
	rawError := fmt.Errorf("azure cloud provider throttled for operation %s with reason %q", operation, reason)
	return &Error{
		Retriable:  true,
		RawError:   rawError,
		RetryAfter: retryAfter,
	}
}
func GetError(resp *http.Response, err error) *Error {
	if err == nil && resp == nil {
		return nil
	}
	if err == nil && resp != nil && isSuccessHTTPResponse(resp) {
		return nil
	}
	retryAfter := time.Time{}
	if retryAfterDuration := getRetryAfter(resp); retryAfterDuration != 0 {
		retryAfter = now().Add(retryAfterDuration)
	}
	return &Error{
		RawError:       getRawError(resp, err),
		RetryAfter:     retryAfter,
		Retriable:      shouldRetryHTTPRequest(resp, err),
		HTTPStatusCode: getHTTPStatusCode(resp),
	}
}
func isSuccessHTTPResponse(resp *http.Response) bool {
	if resp == nil {
		return false
	}
	if 199 < resp.StatusCode && resp.StatusCode < 300 {
		return true
	}
	return false
}
func getRawError(resp *http.Response, err error) error {
	if err != nil {
		return err
	}
	if resp == nil || resp.Body == nil {
		return fmt.Errorf("empty HTTP response")
	}
	defer resp.Body.Close()
	respBody, _ := ioutil.ReadAll(resp.Body)
	resp.Body = ioutil.NopCloser(bytes.NewReader(respBody))
	if len(respBody) == 0 {
		return fmt.Errorf("HTTP status code (%d)", resp.StatusCode)
	}
	return fmt.Errorf("%s", string(respBody))
}
func getHTTPStatusCode(resp *http.Response) int {
	if resp == nil {
		return -1
	}
	return resp.StatusCode
}
func shouldRetryHTTPRequest(resp *http.Response, err error) bool {
	if resp != nil {
		for _, code := range StatusCodesForRetry {
			if resp.StatusCode == code {
				return true
			}
		}
		if isSuccessHTTPResponse(resp) && err != nil {
			return true
		}
		return false
	}
	if err != nil {
		return true
	}
	return false
}
func getRetryAfter(resp *http.Response) time.Duration {
	if resp == nil {
		return 0
	}
	ra := resp.Header.Get(RetryAfterHeaderKey)
	if ra == "" {
		return 0
	}
	var dur time.Duration
	if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 {
		dur = time.Duration(retryAfter) * time.Second
	} else if t, err := time.Parse(time.RFC1123, ra); err == nil {
		dur = t.Sub(now())
	}
	return dur
}
func GetErrorWithRetriableHTTPStatusCodes(resp *http.Response, err error, retriableHTTPStatusCodes []int) *Error {
	rerr := GetError(resp, err)
	if rerr == nil {
		return nil
	}
	for _, code := range retriableHTTPStatusCodes {
		if rerr.HTTPStatusCode == code {
			rerr.Retriable = true
			break
		}
	}
	return rerr
}
func GetStatusNotFoundAndForbiddenIgnoredError(resp *http.Response, err error) *Error {
	rerr := GetError(resp, err)
	if rerr == nil {
		return nil
	}
	if rerr.HTTPStatusCode == http.StatusNotFound {
		klog.V(3).Infof("Ignoring StatusNotFound error: %v", rerr)
		return nil
	}
	if rerr.HTTPStatusCode == http.StatusForbidden {
		klog.V(3).Infof("Ignoring StatusForbidden error: %v", rerr)
		return nil
	}
	return rerr
}
func IsErrorRetriable(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(err.Error(), "Retriable: true")
}