moby/graph/push.go
Aaron Lehmann 810d3b2642 Avoid redundant HEAD requests for identical layers on push
pushV2Tag already deduplicates layers, but the scope of this
deduplication is only for a particular tag. If we are pushing all tags
in a repository, we may check layers several times. Fix this by moving
the layersSeen map from the pushV2Tag function to the v2Pusher struct.

In addition to avoiding some useless round-trips, this makes the "docker
push" output less confusing. It formerly could contain many repeated
lines like:

    124e2127157f: Image already exists
    124e2127157f: Image already exists
    ...

Add test coverage based on the "docker push" output: a hash should not
appear multiple times when pushing multiple tags.

Fixes #14873

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2015-07-22 17:54:20 -07:00

108 lines
3 KiB
Go

package graph
import (
"fmt"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/registry"
)
type ImagePushConfig struct {
MetaHeaders map[string][]string
AuthConfig *cliconfig.AuthConfig
Tag string
OutStream io.Writer
}
type Pusher interface {
// Push tries to push the image configured at the creation of Pusher.
// Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint.
//
// TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic.
Push() (fallback bool, err error)
}
func (s *TagStore) NewPusher(endpoint registry.APIEndpoint, localRepo Repository, repoInfo *registry.RepositoryInfo, imagePushConfig *ImagePushConfig, sf *streamformatter.StreamFormatter) (Pusher, error) {
switch endpoint.Version {
case registry.APIVersion2:
return &v2Pusher{
TagStore: s,
endpoint: endpoint,
localRepo: localRepo,
repoInfo: repoInfo,
config: imagePushConfig,
sf: sf,
layersSeen: make(map[string]bool),
}, nil
case registry.APIVersion1:
return &v1Pusher{
TagStore: s,
endpoint: endpoint,
localRepo: localRepo,
repoInfo: repoInfo,
config: imagePushConfig,
sf: sf,
}, nil
}
return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
}
// FIXME: Allow to interrupt current push when new push of same image is done.
func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error {
var sf = streamformatter.NewJSONStreamFormatter()
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := s.registryService.ResolveRepository(localName)
if err != nil {
return err
}
endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
if err != nil {
return err
}
reposLen := 1
if imagePushConfig.Tag == "" {
reposLen = len(s.Repositories[repoInfo.LocalName])
}
imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
// If it fails, try to get the repository
localRepo, exists := s.Repositories[repoInfo.LocalName]
if !exists {
return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName)
}
var lastErr error
for _, endpoint := range endpoints {
logrus.Debugf("Trying to push %s to %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version)
pusher, err := s.NewPusher(endpoint, localRepo, repoInfo, imagePushConfig, sf)
if err != nil {
lastErr = err
continue
}
if fallback, err := pusher.Push(); err != nil {
if fallback {
lastErr = err
continue
}
logrus.Debugf("Not continuing with error: %v", err)
return err
}
s.eventsService.Log("push", repoInfo.LocalName, "")
return nil
}
if lastErr == nil {
lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.CanonicalName)
}
return lastErr
}