Fix layer exclusion bug on multiple tag push

Ensure that layers are not excluded from manifests based on previous pushes.
Continue skipping pushes on layers which were pushed by a previous tag.

Update push multiple tag tests.
Ensure that each tag pushed exists on the registry and is pullable.
Add output comparison on multiple tag push check.

fixes #15536

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2015-08-12 16:02:12 -07:00 committed by Aaron Lehmann
parent 72f355e466
commit a0d9ffd6f7
2 changed files with 25 additions and 6 deletions

View File

@ -5,6 +5,7 @@ import (
"io" "io"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
@ -44,12 +45,13 @@ func (s *TagStore) NewPusher(endpoint registry.APIEndpoint, localRepo Repository
switch endpoint.Version { switch endpoint.Version {
case registry.APIVersion2: case registry.APIVersion2:
return &v2Pusher{ return &v2Pusher{
TagStore: s, TagStore: s,
endpoint: endpoint, endpoint: endpoint,
localRepo: localRepo, localRepo: localRepo,
repoInfo: repoInfo, repoInfo: repoInfo,
config: imagePushConfig, config: imagePushConfig,
sf: sf, sf: sf,
layersPushed: make(map[digest.Digest]bool),
}, nil }, nil
case registry.APIVersion1: case registry.APIVersion1:
return &v1Pusher{ return &v1Pusher{

View File

@ -27,6 +27,11 @@ type v2Pusher struct {
config *ImagePushConfig config *ImagePushConfig
sf *streamformatter.StreamFormatter sf *streamformatter.StreamFormatter
repo distribution.Repository repo distribution.Repository
// layersPushed is the set of layers known to exist on the remote side.
// This avoids redundant queries when pushing multiple tags that
// involve the same layers.
layersPushed map[digest.Digest]bool
} }
func (p *v2Pusher) Push() (fallback bool, err error) { func (p *v2Pusher) Push() (fallback bool, err error) {
@ -117,6 +122,10 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
return err return err
} }
// break early if layer has already been seen in this image,
// this prevents infinite loops on layers which loopback, this
// cannot be prevented since layer IDs are not merkle hashes
// TODO(dmcgowan): throw error if no valid use case is found
if layersSeen[layer.ID] { if layersSeen[layer.ID] {
break break
} }
@ -138,6 +147,13 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
dgst, err := p.graph.GetDigest(layer.ID) dgst, err := p.graph.GetDigest(layer.ID)
switch err { switch err {
case nil: case nil:
if p.layersPushed[dgst] {
exists = true
// break out of switch, it is already known that
// the push is not needed and therefore doing a
// stat is unnecessary
break
}
_, err := p.repo.Blobs(context.Background()).Stat(context.Background(), dgst) _, err := p.repo.Blobs(context.Background()).Stat(context.Background(), dgst)
switch err { switch err {
case nil: case nil:
@ -173,6 +189,7 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
m.History = append(m.History, manifest.History{V1Compatibility: string(jsonData)}) m.History = append(m.History, manifest.History{V1Compatibility: string(jsonData)})
layersSeen[layer.ID] = true layersSeen[layer.ID] = true
p.layersPushed[dgst] = true
} }
logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID()) logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())