Update version of go-containerregistry. (#724)
Brings in a change from upstream to resolve ports to well-known values when comparing Host values to decide whether or not to send the Bearer Authorization header when pushing an image. Upstream issue is https://github.com/google/go-containerregistry/issues/472.
This commit is contained in:
parent
3422d5572a
commit
80421f2a73
|
|
@ -445,11 +445,13 @@
|
|||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ccc9b3dfd6b951b46e6e1c499af589fbc35c7e1172d6d840cbe836ae08d3536"
|
||||
digest = "1:16c8837e951303ef6388132bc875337660a48ea2dedf1c941ca118ea92d2a3d2"
|
||||
name = "github.com/google/go-containerregistry"
|
||||
packages = [
|
||||
"pkg/authn",
|
||||
"pkg/authn/k8schain",
|
||||
"pkg/internal/retry",
|
||||
"pkg/logs",
|
||||
"pkg/name",
|
||||
"pkg/v1",
|
||||
"pkg/v1/daemon",
|
||||
|
|
@ -465,7 +467,7 @@
|
|||
"pkg/v1/v1util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "bb17f50c1bc6808972811ed2894ecaaeb5de68ad"
|
||||
revision = "273af77a08b28b49cc2cff2dd8ae50a5094dac74"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f4f203acd8b11b8747bdcd91696a01dbc95ccb9e2ca2db6abf81c3a4f5e950ce"
|
||||
|
|
@ -1384,6 +1386,7 @@
|
|||
"golang.org/x/oauth2",
|
||||
"golang.org/x/sync/errgroup",
|
||||
"gopkg.in/src-d/go-git.v4",
|
||||
"gopkg.in/src-d/go-git.v4/plumbing",
|
||||
"k8s.io/client-go/discovery",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ required = [
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/google/go-containerregistry"
|
||||
revision = "bb17f50c1bc6808972811ed2894ecaaeb5de68ad"
|
||||
revision = "273af77a08b28b49cc2cff2dd8ae50a5094dac74"
|
||||
|
||||
[[override]]
|
||||
name = "k8s.io/apimachinery"
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
)
|
||||
|
||||
|
|
@ -100,19 +100,19 @@ var (
|
|||
func (dk *defaultKeychain) Resolve(reg name.Registry) (Authenticator, error) {
|
||||
dir, err := configDir()
|
||||
if err != nil {
|
||||
log.Printf("Unable to determine config dir: %v", err)
|
||||
logs.Warn.Printf("Unable to determine config dir: %v", err)
|
||||
return Anonymous, nil
|
||||
}
|
||||
file := filepath.Join(dir, "config.json")
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Printf("Unable to read %q: %v", file, err)
|
||||
logs.Warn.Printf("Unable to read %q: %v", file, err)
|
||||
return Anonymous, nil
|
||||
}
|
||||
|
||||
var cf cfg
|
||||
if err := json.Unmarshal(content, &cf); err != nil {
|
||||
log.Printf("Unable to parse %q: %v", file, err)
|
||||
logs.Warn.Printf("Unable to parse %q: %v", file, err)
|
||||
return Anonymous, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
68
vendor/github.com/google/go-containerregistry/pkg/internal/retry/retry.go
generated
vendored
Normal file
68
vendor/github.com/google/go-containerregistry/pkg/internal/retry/retry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2019 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package retry provides methods for retrying operations. It is a thin wrapper
|
||||
// around k8s.io/apimachinery/pkg/util/wait to make certain operations easier.
|
||||
package retry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
// This is implemented by several errors in the net package as well as our
|
||||
// transport.Error.
|
||||
type temporary interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
// IsTemporary returns true if err implements Temporary() and it returns true.
|
||||
func IsTemporary(err error) bool {
|
||||
if te, ok := err.(temporary); ok && te.Temporary() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNotNil returns true if err is not nil.
|
||||
func IsNotNil(err error) bool {
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// Predicate determines whether an error should be retried.
|
||||
type Predicate func(error) (retry bool)
|
||||
|
||||
// Retry retries a given function, f, until a predicate is satisfied, using
|
||||
// exponential backoff. If the predicate is never satisfied, it will return the
|
||||
// last error returned by f.
|
||||
func Retry(f func() error, p Predicate, backoff wait.Backoff) (err error) {
|
||||
if f == nil {
|
||||
return fmt.Errorf("nil f passed to retry")
|
||||
}
|
||||
if p == nil {
|
||||
return fmt.Errorf("nil p passed to retry")
|
||||
}
|
||||
|
||||
condition := func() (bool, error) {
|
||||
err = f()
|
||||
if p(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
wait.ExponentialBackoff(backoff, condition)
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package logs exposes the loggers used by this library.
|
||||
package logs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
// Warn is used to log non-fatal errors.
|
||||
Warn = log.New(ioutil.Discard, "", log.LstdFlags)
|
||||
|
||||
// Progress is used to log notable, successful events.
|
||||
Progress = log.New(ioutil.Discard, "", log.LstdFlags)
|
||||
)
|
||||
|
|
@ -77,6 +77,8 @@ func Config(base v1.Image, cfg v1.Config) (v1.Image, error) {
|
|||
}
|
||||
|
||||
cf.Config = cfg
|
||||
// Downstream tooling expects these to match.
|
||||
cf.ContainerConfig = cfg
|
||||
|
||||
return ConfigFile(base, cf)
|
||||
}
|
||||
|
|
@ -468,7 +470,7 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) {
|
|||
|
||||
// Copy basic config over
|
||||
cfg.Config = ocf.Config
|
||||
cfg.ContainerConfig = ocf.ContainerConfig
|
||||
cfg.ContainerConfig = ocf.Config // Downstream tooling expects these to match.
|
||||
|
||||
// Strip away timestamps from the config file
|
||||
cfg.Created = v1.Time{Time: t}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ type Platform struct {
|
|||
OSVersion string `json:"os.version,omitempty"`
|
||||
OSFeatures []string `json:"os.features,omitempty"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
Features []string `json:"features,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,12 +225,16 @@ func (f *fetcher) fetchManifest(ref name.Reference, acceptable []types.MediaType
|
|||
}
|
||||
|
||||
mediaType := types.MediaType(resp.Header.Get("Content-Type"))
|
||||
contentDigest, err := v1.NewHash(resp.Header.Get("Docker-Content-Digest"))
|
||||
if err == nil && mediaType == types.DockerManifestSchema1Signed {
|
||||
// If we can parse the digest from the header, and it's a signed schema 1
|
||||
// manifest, let's use that for the digest to appease older registries.
|
||||
digest = contentDigest
|
||||
}
|
||||
|
||||
// Validate the digest matches what we asked for, if pulling by digest.
|
||||
if dgst, ok := ref.(name.Digest); ok {
|
||||
if mediaType == types.DockerManifestSchema1Signed {
|
||||
// Digests for this are stupid to calculate, ignore it.
|
||||
} else if digest.String() != dgst.DigestStr() {
|
||||
if digest.String() != dgst.DigestStr() {
|
||||
return nil, nil, fmt.Errorf("manifest digest: %q does not match requested digest: %q for %q", digest, dgst.DigestStr(), f.Ref)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func (r *remoteIndex) imageByPlatform(platform v1.Platform) (v1.Image, error) {
|
|||
return desc.Image()
|
||||
}
|
||||
|
||||
// This naively matches the first manifest with matching Architecture and OS.
|
||||
// This naively matches the first manifest with matching platform attributes.
|
||||
//
|
||||
// We should probably use this instead:
|
||||
// github.com/containerd/containerd/platforms
|
||||
|
|
@ -138,7 +138,7 @@ func (r *remoteIndex) childByPlatform(platform v1.Platform) (*Descriptor, error)
|
|||
p = *childDesc.Platform
|
||||
}
|
||||
|
||||
if platform.Architecture == p.Architecture && platform.OS == p.OS {
|
||||
if matchesPlatform(p, platform) {
|
||||
return r.childDescriptor(childDesc, platform)
|
||||
}
|
||||
}
|
||||
|
|
@ -182,3 +182,49 @@ func (r *remoteIndex) childDescriptor(child v1.Descriptor, platform v1.Platform)
|
|||
platform: platform,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// matchesPlatform checks if the given platform matches the required platforms.
|
||||
// The given platform matches the required platform if
|
||||
// - architecture and OS are identical.
|
||||
// - OS version and variant are identical if provided.
|
||||
// - features and OS features of the required platform are subsets of those of the given platform.
|
||||
func matchesPlatform(given, required v1.Platform) bool {
|
||||
// Required fields that must be identical.
|
||||
if given.Architecture != required.Architecture || given.OS != required.OS {
|
||||
return false
|
||||
}
|
||||
|
||||
// Optional fields that may be empty, but must be identical if provided.
|
||||
if required.OSVersion != "" && given.OSVersion != required.OSVersion {
|
||||
return false
|
||||
}
|
||||
if required.Variant != "" && given.Variant != required.Variant {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify required platform's features are a subset of given platform's features.
|
||||
if !isSubset(given.OSFeatures, required.OSFeatures) {
|
||||
return false
|
||||
}
|
||||
if !isSubset(given.Features, required.Features) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isSubset checks if the required array of strings is a subset of the given lst.
|
||||
func isSubset(lst, required []string) bool {
|
||||
set := make(map[string]bool)
|
||||
for _, value := range lst {
|
||||
set[value] = true
|
||||
}
|
||||
|
||||
for _, value := range required {
|
||||
if _, ok := set[value]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@
|
|||
package remote
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
)
|
||||
|
||||
// Option is a functional option for remote operations.
|
||||
|
|
@ -52,11 +53,14 @@ func makeOptions(reg name.Registry, opts ...Option) (*options, error) {
|
|||
return nil, err
|
||||
}
|
||||
if auth == authn.Anonymous {
|
||||
log.Println("No matching credentials were found, falling back on anonymous")
|
||||
logs.Warn.Println("No matching credentials were found, falling back on anonymous")
|
||||
}
|
||||
o.auth = auth
|
||||
}
|
||||
|
||||
// Wrap the transport in something that can retry network flakes.
|
||||
o.transport = transport.NewRetry(o.transport)
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (bt *basicTransport) RoundTrip(in *http.Request) (*http.Response, error) {
|
|||
// we are redirected, only set it when the authorization header matches
|
||||
// the host with which we are interacting.
|
||||
// In case of redirect http.Client can use an empty Host, check URL too.
|
||||
if in.Host == bt.target || in.URL.Host == bt.target {
|
||||
if hdr != "" && (in.Host == bt.target || in.URL.Host == bt.target) {
|
||||
in.Header.Set("Authorization", hdr)
|
||||
}
|
||||
in.Header.Set("User-Agent", transportName)
|
||||
|
|
|
|||
37
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go
generated
vendored
37
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go
generated
vendored
|
|
@ -18,8 +18,10 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
|
|
@ -45,6 +47,11 @@ type bearerTransport struct {
|
|||
|
||||
var _ http.RoundTripper = (*bearerTransport)(nil)
|
||||
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper
|
||||
func (bt *bearerTransport) RoundTrip(in *http.Request) (*http.Response, error) {
|
||||
sendRequest := func() (*http.Response, error) {
|
||||
|
|
@ -58,7 +65,10 @@ func (bt *bearerTransport) RoundTrip(in *http.Request) (*http.Response, error) {
|
|||
// we are redirected, only set it when the authorization header matches
|
||||
// the registry with which we are interacting.
|
||||
// In case of redirect http.Client can use an empty Host, check URL too.
|
||||
if in.Host == bt.registry.RegistryStr() || in.URL.Host == bt.registry.RegistryStr() {
|
||||
canonicalHeaderHost := bt.canonicalAddress(in.Host)
|
||||
canonicalURLHost := bt.canonicalAddress(in.URL.Host)
|
||||
canonicalRegistryHost := bt.canonicalAddress(bt.registry.RegistryStr())
|
||||
if canonicalHeaderHost == canonicalRegistryHost || canonicalURLHost == canonicalRegistryHost {
|
||||
in.Header.Set("Authorization", hdr)
|
||||
|
||||
// When we ping() the registry, we determine whether to use http or https
|
||||
|
|
@ -144,3 +154,28 @@ func (bt *bearerTransport) refresh() error {
|
|||
bt.bearer = &bearer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bt *bearerTransport) canonicalAddress(host string) (address string) {
|
||||
// The host may be any one of:
|
||||
// - hostname
|
||||
// - hostname:port
|
||||
// - ipv4
|
||||
// - ipv4:port
|
||||
// - ipv6
|
||||
// - [ipv6]:port
|
||||
// As net.SplitHostPort returns an error if the host does not contain a port, we should only attempt
|
||||
// to call it when we know that the address contains a port
|
||||
if strings.Count(host, ":") == 1 || (strings.Count(host, ":") >= 2 && strings.Contains(host, "]:")) {
|
||||
hostname, port, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return host
|
||||
}
|
||||
if port == "" {
|
||||
port = portMap[bt.scheme]
|
||||
}
|
||||
|
||||
return net.JoinHostPort(hostname, port)
|
||||
}
|
||||
|
||||
return net.JoinHostPort(host, portMap[bt.scheme])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ func (e *Error) Error() string {
|
|||
}
|
||||
}
|
||||
|
||||
// ShouldRetry returns whether the request that preceded the error should be retried.
|
||||
func (e *Error) ShouldRetry() bool {
|
||||
// Temporary returns whether the request that preceded the error is temporary.
|
||||
func (e *Error) Temporary() bool {
|
||||
if len(e.Errors) == 0 {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
89
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/retry.go
generated
vendored
Normal file
89
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/retry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/internal/retry"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
// Sleep for 0.1, 0.3, 0.9, 2.7 seconds. This should cover networking blips.
|
||||
var defaultBackoff = wait.Backoff{
|
||||
Duration: 100 * time.Millisecond,
|
||||
Factor: 3.0,
|
||||
Jitter: 0.1,
|
||||
Steps: 5,
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = (*retryTransport)(nil)
|
||||
|
||||
// retryTransport wraps a RoundTripper and retries temporary network errors.
|
||||
type retryTransport struct {
|
||||
inner http.RoundTripper
|
||||
backoff wait.Backoff
|
||||
predicate retry.Predicate
|
||||
}
|
||||
|
||||
// Option is a functional option for retryTransport.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
backoff wait.Backoff
|
||||
predicate retry.Predicate
|
||||
}
|
||||
|
||||
// WithRetryBackoff sets the backoff for retry operations.
|
||||
func WithRetryBackoff(backoff wait.Backoff) Option {
|
||||
return func(o *options) {
|
||||
o.backoff = backoff
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryPredicate sets the predicate for retry operations.
|
||||
func WithRetryPredicate(predicate func(error) bool) Option {
|
||||
return func(o *options) {
|
||||
o.predicate = predicate
|
||||
}
|
||||
}
|
||||
|
||||
// NewRetry returns a transport that retries errors.
|
||||
func NewRetry(inner http.RoundTripper, opts ...Option) http.RoundTripper {
|
||||
o := &options{
|
||||
backoff: defaultBackoff,
|
||||
predicate: retry.IsTemporary,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
return &retryTransport{
|
||||
inner: inner,
|
||||
backoff: o.backoff,
|
||||
predicate: o.predicate,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *retryTransport) RoundTrip(in *http.Request) (out *http.Response, err error) {
|
||||
roundtrip := func() error {
|
||||
out, err = t.inner.RoundTrip(in)
|
||||
return err
|
||||
}
|
||||
retry.Retry(roundtrip, t.predicate, t.backoff)
|
||||
return
|
||||
}
|
||||
|
|
@ -19,18 +19,19 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/internal/retry"
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
type manifest interface {
|
||||
|
|
@ -297,7 +298,7 @@ func (w *writer) uploadOne(l v1.Layer) error {
|
|||
return err
|
||||
}
|
||||
if existing {
|
||||
log.Printf("existing blob: %v", h)
|
||||
logs.Progress.Printf("existing blob: %v", h)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +319,7 @@ func (w *writer) uploadOne(l v1.Layer) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("mounted blob: %s", h.String())
|
||||
logs.Progress.Printf("mounted blob: %s", h.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -340,25 +341,19 @@ func (w *writer) uploadOne(l v1.Layer) error {
|
|||
if err := w.commitBlob(location, digest); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("pushed blob: %s", digest)
|
||||
logs.Progress.Printf("pushed blob: %s", digest)
|
||||
return nil
|
||||
}
|
||||
const maxRetries = 2
|
||||
const backoffFactor = 0.5
|
||||
retries := 0
|
||||
for {
|
||||
err := tryUpload()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if te, ok := err.(*transport.Error); !(ok && te.ShouldRetry()) || retries >= maxRetries {
|
||||
return err
|
||||
}
|
||||
log.Printf("retrying after error: %s", err)
|
||||
retries++
|
||||
duration := time.Duration(backoffFactor*math.Pow(2, float64(retries))) * time.Second
|
||||
time.Sleep(duration)
|
||||
|
||||
// Try this three times, waiting 1s after first failure, 3s after second.
|
||||
backoff := wait.Backoff{
|
||||
Duration: 1.0 * time.Second,
|
||||
Factor: 3.0,
|
||||
Jitter: 0.1,
|
||||
Steps: 3,
|
||||
}
|
||||
|
||||
return retry.Retry(tryUpload, retry.IsTemporary, backoff)
|
||||
}
|
||||
|
||||
// commitImage does a PUT of the image's manifest.
|
||||
|
|
@ -397,7 +392,7 @@ func (w *writer) commitImage(man manifest) error {
|
|||
}
|
||||
|
||||
// The image was successfully pushed!
|
||||
log.Printf("%v: digest: %v size: %d", w.ref, digest, len(raw))
|
||||
logs.Progress.Printf("%v: digest: %v size: %d", w.ref, digest, len(raw))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +453,7 @@ func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
|
|||
return err
|
||||
}
|
||||
if exists {
|
||||
log.Printf("existing manifest: %v", desc.Digest)
|
||||
logs.Progress.Printf("existing manifest: %v", desc.Digest)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue