1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package remote
16
17 import (
18 "context"
19 "errors"
20 "io"
21 "net"
22 "net/http"
23 "syscall"
24 "time"
25
26 "github.com/google/go-containerregistry/internal/retry"
27 "github.com/google/go-containerregistry/pkg/authn"
28 "github.com/google/go-containerregistry/pkg/logs"
29 v1 "github.com/google/go-containerregistry/pkg/v1"
30 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
31 )
32
33
34 type Option func(*options) error
35
36 type options struct {
37 auth authn.Authenticator
38 keychain authn.Keychain
39 transport http.RoundTripper
40 context context.Context
41 jobs int
42 userAgent string
43 allowNondistributableArtifacts bool
44 progress *progress
45 retryBackoff Backoff
46 retryPredicate retry.Predicate
47 retryStatusCodes []int
48
49
50 platform v1.Platform
51 pageSize int
52 filter map[string]string
53
54
55 puller *Puller
56 pusher *Pusher
57 }
58
59 var defaultPlatform = v1.Platform{
60 Architecture: "amd64",
61 OS: "linux",
62 }
63
64
65 type Backoff = retry.Backoff
66
67 var defaultRetryPredicate retry.Predicate = func(err error) bool {
68
69
70 if retry.IsTemporary(err) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) || errors.Is(err, net.ErrClosed) {
71 logs.Warn.Printf("retrying %v", err)
72 return true
73 }
74 return false
75 }
76
77
78 var defaultRetryBackoff = Backoff{
79 Duration: 1.0 * time.Second,
80 Factor: 3.0,
81 Jitter: 0.1,
82 Steps: 3,
83 }
84
85
86 var fastBackoff = Backoff{
87 Duration: 1.0 * time.Millisecond,
88 Factor: 3.0,
89 Jitter: 0.1,
90 Steps: 3,
91 }
92
93 var defaultRetryStatusCodes = []int{
94 http.StatusRequestTimeout,
95 http.StatusInternalServerError,
96 http.StatusBadGateway,
97 http.StatusServiceUnavailable,
98 http.StatusGatewayTimeout,
99 499,
100 522,
101 }
102
103 const (
104 defaultJobs = 4
105
106
107
108 defaultPageSize = 1000
109 )
110
111
112
113 var DefaultTransport http.RoundTripper = &http.Transport{
114 Proxy: http.ProxyFromEnvironment,
115 DialContext: (&net.Dialer{
116 Timeout: 30 * time.Second,
117 KeepAlive: 30 * time.Second,
118 }).DialContext,
119 ForceAttemptHTTP2: true,
120 MaxIdleConns: 100,
121 IdleConnTimeout: 90 * time.Second,
122 TLSHandshakeTimeout: 10 * time.Second,
123 ExpectContinueTimeout: 1 * time.Second,
124
125 MaxIdleConnsPerHost: 50,
126 }
127
128 func makeOptions(opts ...Option) (*options, error) {
129 o := &options{
130 transport: DefaultTransport,
131 platform: defaultPlatform,
132 context: context.Background(),
133 jobs: defaultJobs,
134 pageSize: defaultPageSize,
135 retryPredicate: defaultRetryPredicate,
136 retryBackoff: defaultRetryBackoff,
137 retryStatusCodes: defaultRetryStatusCodes,
138 }
139
140 for _, option := range opts {
141 if err := option(o); err != nil {
142 return nil, err
143 }
144 }
145
146 switch {
147 case o.auth != nil && o.keychain != nil:
148
149
150 return nil, errors.New("provide an option for either authn.Authenticator or authn.Keychain, not both")
151 case o.auth == nil:
152 o.auth = authn.Anonymous
153 }
154
155
156
157 if _, ok := o.transport.(*transport.Wrapper); !ok {
158
159
160
161 if logs.Enabled(logs.Debug) {
162 o.transport = transport.NewLogger(o.transport)
163 }
164
165
166 o.transport = transport.NewRetry(o.transport, transport.WithRetryPredicate(defaultRetryPredicate), transport.WithRetryStatusCodes(o.retryStatusCodes...))
167
168
169 if o.userAgent != "" {
170 o.transport = transport.NewUserAgent(o.transport, o.userAgent)
171 }
172 }
173
174 return o, nil
175 }
176
177
178
179
180
181
182
183 func WithTransport(t http.RoundTripper) Option {
184 return func(o *options) error {
185 o.transport = t
186 return nil
187 }
188 }
189
190
191
192
193
194
195 func WithAuth(auth authn.Authenticator) Option {
196 return func(o *options) error {
197 o.auth = auth
198 return nil
199 }
200 }
201
202
203
204
205
206
207
208 func WithAuthFromKeychain(keys authn.Keychain) Option {
209 return func(o *options) error {
210 o.keychain = keys
211 return nil
212 }
213 }
214
215
216
217
218
219 func WithPlatform(p v1.Platform) Option {
220 return func(o *options) error {
221 o.platform = p
222 return nil
223 }
224 }
225
226
227
228
229
230
231
232
233 func WithContext(ctx context.Context) Option {
234 return func(o *options) error {
235 o.context = ctx
236 return nil
237 }
238 }
239
240
241
242
243
244
245 func WithJobs(jobs int) Option {
246 return func(o *options) error {
247 if jobs <= 0 {
248 return errors.New("jobs must be greater than zero")
249 }
250 o.jobs = jobs
251 return nil
252 }
253 }
254
255
256
257
258
259 func WithUserAgent(ua string) Option {
260 return func(o *options) error {
261 o.userAgent = ua
262 return nil
263 }
264 }
265
266
267
268
269
270
271 func WithNondistributable(o *options) error {
272 o.allowNondistributableArtifacts = true
273 return nil
274 }
275
276
277
278
279
280 func WithProgress(updates chan<- v1.Update) Option {
281 return func(o *options) error {
282 o.progress = &progress{updates: updates}
283 o.progress.lastUpdate = &v1.Update{}
284 return nil
285 }
286 }
287
288
289
290
291
292 func WithPageSize(size int) Option {
293 return func(o *options) error {
294 o.pageSize = size
295 return nil
296 }
297 }
298
299
300 func WithRetryBackoff(backoff Backoff) Option {
301 return func(o *options) error {
302 o.retryBackoff = backoff
303 return nil
304 }
305 }
306
307
308 func WithRetryPredicate(predicate retry.Predicate) Option {
309 return func(o *options) error {
310 o.retryPredicate = predicate
311 return nil
312 }
313 }
314
315
316 func WithRetryStatusCodes(codes ...int) Option {
317 return func(o *options) error {
318 o.retryStatusCodes = codes
319 return nil
320 }
321 }
322
323
324 func WithFilter(key string, value string) Option {
325 return func(o *options) error {
326 if o.filter == nil {
327 o.filter = map[string]string{}
328 }
329 o.filter[key] = value
330 return nil
331 }
332 }
333
334
335
336
337
338
339
340 func Reuse[I *Puller | *Pusher](i I) Option {
341 return func(o *options) error {
342 if puller, ok := any(i).(*Puller); ok {
343 o.puller = puller
344 } else if pusher, ok := any(i).(*Pusher); ok {
345 o.pusher = pusher
346 }
347 return nil
348 }
349 }
350
View as plain text