func WithBodyFunc(getbody func() (io.ReadCloser, error)) OptsReq {
	return func(req *request) {
		req.getBody = getbody
	}
}
WithBodyFunc includes body content in a request
package retryable
import (
	"bytes"
	"context"
	"sync"
	_ "crypto/sha256"
	_ "crypto/sha512"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
	digest "github.com/opencontainers/go-digest"
	"github.com/sirupsen/logrus"
)
type Retryable interface {
	DoRequest(ctx context.Context, method string, u []url.URL, opts ...OptsReq) (Response, error)
	BackoffClear()
	BackoffUntil() time.Time
}
type Response interface {
	io.ReadCloser
	HTTPResponse() *http.Response
	HTTPResponses() ([]*http.Response, error)
}
type Auth interface {
	AddScope(host, scope string) error
	HandleResponse(*http.Response) error
	UpdateRequest(*http.Request) error
}
type Opts func(*retryable)
type OptsReq func(*request)
type retryable struct {
	httpClient    *http.Client
	auth          Auth
	rootCAPool    [][]byte
	limit         int
	delayInit     time.Duration
	delayMax      time.Duration
	backoffNeeded bool
	backoffCur    int
	backoffUntil  time.Time
	log           *logrus.Logger
	useragent     string
	mu            sync.Mutex
}
var defaultDelayInit, _ = time.ParseDuration("1s")
var defaultDelayMax, _ = time.ParseDuration("30s")
var defaultLimit = 3
func NewRetryable(opts ...Opts) Retryable {
	r := &retryable{
		httpClient: &http.Client{},
		limit:      defaultLimit,
		delayInit:  defaultDelayInit,
		delayMax:   defaultDelayMax,
		log:        &logrus.Logger{Out: ioutil.Discard},
		rootCAPool: [][]byte{},
	}
	for _, opt := range opts {
		opt(r)
	}
	if len(r.rootCAPool) > 0 {
		var tlsc *tls.Config
		if r.httpClient.Transport == nil {
			r.httpClient.Transport = &http.Transport{}
		}
		t, ok := r.httpClient.Transport.(*http.Transport)
		if ok {
			if t.TLSClientConfig != nil {
				tlsc = t.TLSClientConfig.Clone()
			} else {
				tlsc = &tls.Config{}
			}
			if tlsc.RootCAs == nil {
				rootPool, err := x509.SystemCertPool()
				if err != nil {
					r.log.WithFields(logrus.Fields{
						"err": err,
					}).Warn("Failed to load system cert pool")
				}
				tlsc.RootCAs = rootPool
			}
			for _, ca := range r.rootCAPool {
				if ok := tlsc.RootCAs.AppendCertsFromPEM(ca); !ok {
					r.log.WithFields(logrus.Fields{
						"cert": string(ca),
					}).Warn("Failed to load root certificate")
				}
			}
			t.TLSClientConfig = tlsc
			r.httpClient.Transport = t
		}
	}
	return r
}
func WithAuth(auth Auth) Opts {
	return func(r *retryable) {
		r.auth = auth
	}
}
func WithCerts(certs [][]byte) Opts {
	return func(r *retryable) {
		for _, c := range certs {
			r.rootCAPool = append(r.rootCAPool, c)
		}
	}
}
func WithCertFiles(files []string) Opts {
	return func(r *retryable) {
		for _, f := range files {
			c, err := ioutil.ReadFile(f)
			if err != nil {
				r.log.WithFields(logrus.Fields{
					"err":  err,
					"file": f,
				}).Warn("Failed to read certificate")
			} else {
				r.rootCAPool = append(r.rootCAPool, c)
			}
		}
	}
}
func WithDelay(delayInit time.Duration, delayMax time.Duration) Opts {
	return func(r *retryable) {
		if delayInit > 0 {
			r.delayInit = delayInit
		}
		if delayMax > r.delayInit {
			r.delayMax = delayMax
		} else if delayMax > 0 {
			r.delayMax = r.delayInit
		} else {
			r.delayMax = r.delayInit * 30
		}
	}
}
func WithHTTPClient(h *http.Client) Opts {
	return func(r *retryable) {
		r.httpClient = h
	}
}
func WithLimit(l int) Opts {
	return func(r *retryable) {
		if l > 0 {
			r.limit = l
		}
	}
}
func WithLog(log *logrus.Logger) Opts {
	return func(r *retryable) {
		r.log = log
	}
}
func WithTransport(t *http.Transport) Opts {
	return func(r *retryable) {
		r.httpClient = &http.Client{Transport: t}
	}
}
func WithUserAgent(ua string) Opts {
	return func(r *retryable) {
		r.useragent = ua
	}
}
func (r *retryable) BackoffClear() {
	if r.backoffCur > r.limit {
		r.backoffCur = r.limit
	}
	if r.backoffCur > 0 {
		r.backoffCur--
		if r.backoffCur == 0 {
			r.backoffUntil = time.Time{}
		}
	}
	r.backoffNeeded = false
}
func (r *retryable) backoffSet(lastResp *http.Response) error {
	r.backoffCur++
	sleepTime := r.delayInit << r.backoffCur
	if sleepTime > r.delayMax {
		sleepTime = r.delayMax
	}
	if lastResp != nil && lastResp.Header.Get("Retry-After") != "" {
		ras := lastResp.Header.Get("Retry-After")
		ra, _ := time.ParseDuration(ras + "s")
		if ra > r.delayMax {
			sleepTime = r.delayMax
		} else if ra > sleepTime {
			sleepTime = ra
		}
	}
	r.backoffUntil = time.Now().Add(sleepTime)
	r.backoffNeeded = true
	if r.backoffCur == r.limit {
		return fmt.Errorf("%w: backoffs %d", ErrBackoffLimit, r.backoffCur)
	}
	return nil
}
func (r *retryable) BackoffUntil() time.Time {
	return r.backoffUntil
}
type request struct {
	r          *retryable
	context    context.Context
	method     string
	urls       []url.URL
	curURL     int
	header     http.Header
	getBody    func() (io.ReadCloser, error)
	contentLen int64
	chunking   bool
	offset     int64
	curRead    int64
	done       bool
	digest     digest.Digest
	digester   digest.Digester
	progressCB func(int64, error)
	responses  []*http.Response
	reader     io.Reader
	log        *logrus.Logger
}
func (r *retryable) DoRequest(ctx context.Context, method string, u []url.URL, opts ...OptsReq) (Response, error) {
	req := &request{
		r:          r,
		context:    ctx,
		method:     method,
		urls:       u,
		curURL:     0,
		header:     http.Header{},
		getBody:    nil,
		contentLen: -1,
		chunking:   false,
		offset:     0,
		curRead:    0,
		done:       false,
		digest:     "",
		digester:   nil,
		progressCB: nil,
		responses:  []*http.Response{},
		reader:     nil,
		log:        r.log,
	}
	for _, opt := range opts {
		opt(req)
	}
	err := req.retryLoop()
	return req, err
}
func WithBodyBytes(body []byte) OptsReq {
	return func(req *request) {
		req.contentLen = int64(len(body))
		req.getBody = func() (io.ReadCloser, error) {
			return ioutil.NopCloser(bytes.NewReader(body)), nil
		}
	}
}