1 // Copyright 2019 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package retry provides methods for retrying operations. It is a thin wrapper 16 // around k8s.io/apimachinery/pkg/util/wait to make certain operations easier. 17 package retry 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 24 "github.com/google/go-containerregistry/internal/retry/wait" 25 ) 26 27 // Backoff is an alias of our own wait.Backoff to avoid name conflicts with 28 // the kubernetes wait package. Typing retry.Backoff is aesier than fixing 29 // the wrong import every time you use wait.Backoff. 30 type Backoff = wait.Backoff 31 32 // This is implemented by several errors in the net package as well as our 33 // transport.Error. 34 type temporary interface { 35 Temporary() bool 36 } 37 38 // IsTemporary returns true if err implements Temporary() and it returns true. 39 func IsTemporary(err error) bool { 40 if errors.Is(err, context.DeadlineExceeded) { 41 return false 42 } 43 if te, ok := err.(temporary); ok && te.Temporary() { 44 return true 45 } 46 return false 47 } 48 49 // IsNotNil returns true if err is not nil. 50 func IsNotNil(err error) bool { 51 return err != nil 52 } 53 54 // Predicate determines whether an error should be retried. 55 type Predicate func(error) (retry bool) 56 57 // Retry retries a given function, f, until a predicate is satisfied, using 58 // exponential backoff. If the predicate is never satisfied, it will return the 59 // last error returned by f. 60 func Retry(f func() error, p Predicate, backoff wait.Backoff) (err error) { 61 if f == nil { 62 return fmt.Errorf("nil f passed to retry") 63 } 64 if p == nil { 65 return fmt.Errorf("nil p passed to retry") 66 } 67 68 condition := func() (bool, error) { 69 err = f() 70 if p(err) { 71 return false, nil 72 } 73 return true, err 74 } 75 76 wait.ExponentialBackoff(backoff, condition) 77 return 78 } 79 80 type contextKey string 81 82 var key = contextKey("never") 83 84 // Never returns a context that signals something should not be retried. 85 // This is a hack and can be used to communicate across package boundaries 86 // to avoid retry amplification. 87 func Never(ctx context.Context) context.Context { 88 return context.WithValue(ctx, key, true) 89 } 90 91 // Ever returns true if the context was wrapped by Never. 92 func Ever(ctx context.Context) bool { 93 return ctx.Value(key) == nil 94 } 95