810d3b2642
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>
108 lines
3 KiB
Go
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
|
|
}
|