mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Do not fall back to the V1 protocol when we know we are talking to a V2 registry
If we detect a Docker-Distribution-Api-Version header indicating that the registry speaks the V2 protocol, no fallback to V1 should take place. The same applies if a V2 registry operation succeeds while attempting a push or pull. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
905f3336b2
commit
a57478d65f
11 changed files with 163 additions and 112 deletions
|
@ -49,7 +49,7 @@ type Puller interface {
|
||||||
// Pull tries to pull the image referenced by `tag`
|
// Pull tries to pull the image referenced by `tag`
|
||||||
// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
|
// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
|
||||||
//
|
//
|
||||||
Pull(ctx context.Context, ref reference.Named) (fallback bool, err error)
|
Pull(ctx context.Context, ref reference.Named) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPuller returns a Puller interface that will pull from either a v1 or v2
|
// newPuller returns a Puller interface that will pull from either a v1 or v2
|
||||||
|
@ -108,8 +108,17 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
|
||||||
// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
|
// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
|
||||||
// error is the ones from v2 endpoints not v1.
|
// error is the ones from v2 endpoints not v1.
|
||||||
discardNoSupportErrors bool
|
discardNoSupportErrors bool
|
||||||
|
|
||||||
|
// confirmedV2 is set to true if a pull attempt managed to
|
||||||
|
// confirm that it was talking to a v2 registry. This will
|
||||||
|
// prevent fallback to the v1 protocol.
|
||||||
|
confirmedV2 bool
|
||||||
)
|
)
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
|
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
|
||||||
|
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
|
logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
|
||||||
|
|
||||||
puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
|
puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
|
||||||
|
@ -117,13 +126,18 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
|
||||||
errors = append(errors, err.Error())
|
errors = append(errors, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fallback, err := puller.Pull(ctx, ref); err != nil {
|
if err := puller.Pull(ctx, ref); err != nil {
|
||||||
// Was this pull cancelled? If so, don't try to fall
|
// Was this pull cancelled? If so, don't try to fall
|
||||||
// back.
|
// back.
|
||||||
|
fallback := false
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
fallback = false
|
|
||||||
default:
|
default:
|
||||||
|
if fallbackErr, ok := err.(fallbackError); ok {
|
||||||
|
fallback = true
|
||||||
|
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
|
||||||
|
err = fallbackErr.err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if fallback {
|
if fallback {
|
||||||
if _, ok := err.(registry.ErrNoSupport); !ok {
|
if _, ok := err.(registry.ErrNoSupport); !ok {
|
||||||
|
|
|
@ -33,15 +33,15 @@ type v1Puller struct {
|
||||||
session *registry.Session
|
session *registry.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool, err error) {
|
func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
|
||||||
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||||
// Allowing fallback, because HTTPS v1 is before HTTP v2
|
// Allowing fallback, because HTTPS v1 is before HTTP v2
|
||||||
return true, registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}
|
return fallbackError{err: registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
||||||
tr := transport.NewTransport(
|
tr := transport.NewTransport(
|
||||||
|
@ -53,21 +53,21 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool
|
||||||
v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
|
v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
||||||
return true, err
|
return fallbackError{err: err}
|
||||||
}
|
}
|
||||||
p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
|
p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(dmcgowan): Check if should fallback
|
// TODO(dmcgowan): Check if should fallback
|
||||||
logrus.Debugf("Fallback from error: %s", err)
|
logrus.Debugf("Fallback from error: %s", err)
|
||||||
return true, err
|
return fallbackError{err: err}
|
||||||
}
|
}
|
||||||
if err := p.pullRepository(ctx, ref); err != nil {
|
if err := p.pullRepository(ctx, ref); err != nil {
|
||||||
// TODO(dmcgowan): Check if should fallback
|
// TODO(dmcgowan): Check if should fallback
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.")
|
progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.")
|
||||||
|
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
|
func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
|
||||||
|
|
|
@ -32,24 +32,26 @@ type v2Puller struct {
|
||||||
config *ImagePullConfig
|
config *ImagePullConfig
|
||||||
repoInfo *registry.RepositoryInfo
|
repoInfo *registry.RepositoryInfo
|
||||||
repo distribution.Repository
|
repo distribution.Repository
|
||||||
|
// confirmedV2 is set to true if we confirm we're talking to a v2
|
||||||
|
// registry. This is used to limit fallbacks to the v1 protocol.
|
||||||
|
confirmedV2 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool, err error) {
|
func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
||||||
// TODO(tiborvass): was ReceiveTimeout
|
// TODO(tiborvass): was ReceiveTimeout
|
||||||
p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Error getting v2 registry: %v", err)
|
logrus.Warnf("Error getting v2 registry: %v", err)
|
||||||
return true, err
|
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.pullV2Repository(ctx, ref); err != nil {
|
if err = p.pullV2Repository(ctx, ref); err != nil {
|
||||||
if registry.ContinueOnError(err) {
|
if registry.ContinueOnError(err) {
|
||||||
logrus.Debugf("Error trying v2 registry: %v", err)
|
logrus.Debugf("Error trying v2 registry: %v", err)
|
||||||
return true, err
|
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
||||||
}
|
}
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
return false, nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
|
func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
|
||||||
|
@ -67,6 +69,10 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this call succeeded, we can be confident that the
|
||||||
|
// registry on the other side speaks the v2 protocol.
|
||||||
|
p.confirmedV2 = true
|
||||||
|
|
||||||
// This probably becomes a lot nicer after the manifest
|
// This probably becomes a lot nicer after the manifest
|
||||||
// refactor...
|
// refactor...
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
|
@ -208,6 +214,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
|
||||||
if unverifiedManifest == nil {
|
if unverifiedManifest == nil {
|
||||||
return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
|
return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If GetByTag succeeded, we can be confident that the registry on
|
||||||
|
// the other side speaks the v2 protocol.
|
||||||
|
p.confirmedV2 = true
|
||||||
|
|
||||||
var verifiedManifest *schema1.Manifest
|
var verifiedManifest *schema1.Manifest
|
||||||
verifiedManifest, err = verifyManifest(unverifiedManifest, ref)
|
verifiedManifest, err = verifyManifest(unverifiedManifest, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -59,7 +59,7 @@ type Pusher interface {
|
||||||
// Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint.
|
// 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.
|
// TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic.
|
||||||
Push(ctx context.Context) (fallback bool, err error)
|
Push(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const compressionBufSize = 32768
|
const compressionBufSize = 32768
|
||||||
|
@ -116,8 +116,21 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
|
||||||
return fmt.Errorf("Repository does not exist: %s", repoInfo.Name())
|
return fmt.Errorf("Repository does not exist: %s", repoInfo.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastErr error
|
var (
|
||||||
|
lastErr error
|
||||||
|
|
||||||
|
// confirmedV2 is set to true if a push attempt managed to
|
||||||
|
// confirm that it was talking to a v2 registry. This will
|
||||||
|
// prevent fallback to the v1 protocol.
|
||||||
|
confirmedV2 bool
|
||||||
|
)
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
|
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
|
||||||
|
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
|
logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
|
||||||
|
|
||||||
pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
|
pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
|
||||||
|
@ -125,22 +138,22 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
|
||||||
lastErr = err
|
lastErr = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fallback, err := pusher.Push(ctx); err != nil {
|
if err := pusher.Push(ctx); err != nil {
|
||||||
// Was this push cancelled? If so, don't try to fall
|
// Was this push cancelled? If so, don't try to fall
|
||||||
// back.
|
// back.
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
fallback = false
|
|
||||||
default:
|
default:
|
||||||
|
if fallbackErr, ok := err.(fallbackError); ok {
|
||||||
|
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
|
||||||
|
err = fallbackErr.err
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logrus.Debugf("Not continuing with error: %v", err)
|
logrus.Debugf("Not continuing with error: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
|
imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
|
||||||
|
|
|
@ -29,10 +29,10 @@ type v1Pusher struct {
|
||||||
session *registry.Session
|
session *registry.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v1Pusher) Push(ctx context.Context) (fallback bool, err error) {
|
func (p *v1Pusher) Push(ctx context.Context) error {
|
||||||
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
|
||||||
tr := transport.NewTransport(
|
tr := transport.NewTransport(
|
||||||
|
@ -44,18 +44,18 @@ func (p *v1Pusher) Push(ctx context.Context) (fallback bool, err error) {
|
||||||
v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
|
v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
||||||
return true, err
|
return fallbackError{err: err}
|
||||||
}
|
}
|
||||||
p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
|
p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(dmcgowan): Check if should fallback
|
// TODO(dmcgowan): Check if should fallback
|
||||||
return true, err
|
return fallbackError{err: err}
|
||||||
}
|
}
|
||||||
if err := p.pushRepository(ctx); err != nil {
|
if err := p.pushRepository(ctx); err != nil {
|
||||||
// TODO(dmcgowan): Check if should fallback
|
// TODO(dmcgowan): Check if should fallback
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1Image exposes the configuration, filesystem layer ID, and a v1 ID for an
|
// v1Image exposes the configuration, filesystem layer ID, and a v1 ID for an
|
||||||
|
|
|
@ -34,6 +34,10 @@ type v2Pusher struct {
|
||||||
config *ImagePushConfig
|
config *ImagePushConfig
|
||||||
repo distribution.Repository
|
repo distribution.Repository
|
||||||
|
|
||||||
|
// confirmedV2 is set to true if we confirm we're talking to a v2
|
||||||
|
// registry. This is used to limit fallbacks to the v1 protocol.
|
||||||
|
confirmedV2 bool
|
||||||
|
|
||||||
// layersPushed is the set of layers known to exist on the remote side.
|
// layersPushed is the set of layers known to exist on the remote side.
|
||||||
// This avoids redundant queries when pushing multiple tags that
|
// This avoids redundant queries when pushing multiple tags that
|
||||||
// involve the same layers.
|
// involve the same layers.
|
||||||
|
@ -45,18 +49,27 @@ type pushMap struct {
|
||||||
layersPushed map[digest.Digest]bool
|
layersPushed map[digest.Digest]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) {
|
func (p *v2Pusher) Push(ctx context.Context) (err error) {
|
||||||
p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
|
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Error getting v2 registry: %v", err)
|
logrus.Debugf("Error getting v2 registry: %v", err)
|
||||||
return true, err
|
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = p.pushV2Repository(ctx); err != nil {
|
||||||
|
if registry.ContinueOnError(err) {
|
||||||
|
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
|
||||||
var associations []reference.Association
|
var associations []reference.Association
|
||||||
if _, isTagged := p.ref.(reference.NamedTagged); isTagged {
|
if _, isTagged := p.ref.(reference.NamedTagged); isTagged {
|
||||||
imageID, err := p.config.ReferenceStore.Get(p.ref)
|
imageID, err := p.config.ReferenceStore.Get(p.ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("tag does not exist: %s", p.ref.String())
|
return fmt.Errorf("tag does not exist: %s", p.ref.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
associations = []reference.Association{
|
associations = []reference.Association{
|
||||||
|
@ -70,19 +83,19 @@ func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) {
|
||||||
associations = p.config.ReferenceStore.ReferencesByName(p.ref)
|
associations = p.config.ReferenceStore.ReferencesByName(p.ref)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err)
|
return fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err)
|
||||||
}
|
}
|
||||||
if len(associations) == 0 {
|
if len(associations) == 0 {
|
||||||
return false, fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
|
return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, association := range associations {
|
for _, association := range associations {
|
||||||
if err := p.pushV2Tag(ctx, association); err != nil {
|
if err := p.pushV2Tag(ctx, association); err != nil {
|
||||||
return false, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Association) error {
|
func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Association) error {
|
||||||
|
@ -109,30 +122,28 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Associat
|
||||||
|
|
||||||
var descriptors []xfer.UploadDescriptor
|
var descriptors []xfer.UploadDescriptor
|
||||||
|
|
||||||
|
descriptorTemplate := v2PushDescriptor{
|
||||||
|
blobSumService: p.blobSumService,
|
||||||
|
repo: p.repo,
|
||||||
|
layersPushed: &p.layersPushed,
|
||||||
|
confirmedV2: &p.confirmedV2,
|
||||||
|
}
|
||||||
|
|
||||||
// Push empty layer if necessary
|
// Push empty layer if necessary
|
||||||
for _, h := range img.History {
|
for _, h := range img.History {
|
||||||
if h.EmptyLayer {
|
if h.EmptyLayer {
|
||||||
descriptors = []xfer.UploadDescriptor{
|
descriptor := descriptorTemplate
|
||||||
&v2PushDescriptor{
|
descriptor.layer = layer.EmptyLayer
|
||||||
layer: layer.EmptyLayer,
|
descriptors = []xfer.UploadDescriptor{&descriptor}
|
||||||
blobSumService: p.blobSumService,
|
|
||||||
repo: p.repo,
|
|
||||||
layersPushed: &p.layersPushed,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop bounds condition is to avoid pushing the base layer on Windows.
|
// Loop bounds condition is to avoid pushing the base layer on Windows.
|
||||||
for i := 0; i < len(img.RootFS.DiffIDs); i++ {
|
for i := 0; i < len(img.RootFS.DiffIDs); i++ {
|
||||||
descriptor := &v2PushDescriptor{
|
descriptor := descriptorTemplate
|
||||||
layer: l,
|
descriptor.layer = l
|
||||||
blobSumService: p.blobSumService,
|
descriptors = append(descriptors, &descriptor)
|
||||||
repo: p.repo,
|
|
||||||
layersPushed: &p.layersPushed,
|
|
||||||
}
|
|
||||||
descriptors = append(descriptors, descriptor)
|
|
||||||
|
|
||||||
l = l.Parent()
|
l = l.Parent()
|
||||||
}
|
}
|
||||||
|
@ -181,6 +192,7 @@ type v2PushDescriptor struct {
|
||||||
blobSumService *metadata.BlobSumService
|
blobSumService *metadata.BlobSumService
|
||||||
repo distribution.Repository
|
repo distribution.Repository
|
||||||
layersPushed *pushMap
|
layersPushed *pushMap
|
||||||
|
confirmedV2 *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *v2PushDescriptor) Key() string {
|
func (pd *v2PushDescriptor) Key() string {
|
||||||
|
@ -251,6 +263,10 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
|
||||||
return "", retryOnError(err)
|
return "", retryOnError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If Commit succeded, that's an indication that the remote registry
|
||||||
|
// speaks the v2 protocol.
|
||||||
|
*pd.confirmedV2 = true
|
||||||
|
|
||||||
logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
|
logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
|
||||||
progress.Update(progressOutput, pd.ID(), "Pushed")
|
progress.Update(progressOutput, pd.ID(), "Pushed")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package distribution
|
package distribution
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -24,6 +23,22 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fallbackError wraps an error that can possibly allow fallback to a different
|
||||||
|
// endpoint.
|
||||||
|
type fallbackError struct {
|
||||||
|
// err is the error being wrapped.
|
||||||
|
err error
|
||||||
|
// confirmedV2 is set to true if it was confirmed that the registry
|
||||||
|
// supports the v2 protocol. This is used to limit fallbacks to the v1
|
||||||
|
// protocol.
|
||||||
|
confirmedV2 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error renders the FallbackError as a string.
|
||||||
|
func (f fallbackError) Error() string {
|
||||||
|
return f.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
type dumbCredentialStore struct {
|
type dumbCredentialStore struct {
|
||||||
auth *types.AuthConfig
|
auth *types.AuthConfig
|
||||||
}
|
}
|
||||||
|
@ -35,9 +50,7 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) {
|
||||||
// NewV2Repository returns a repository (v2 only). It creates a HTTP transport
|
// NewV2Repository returns a repository (v2 only). It creates a HTTP transport
|
||||||
// providing timeout settings and authentication support, and also verifies the
|
// providing timeout settings and authentication support, and also verifies the
|
||||||
// remote API version.
|
// remote API version.
|
||||||
func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (distribution.Repository, error) {
|
func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
repoName := repoInfo.FullName()
|
repoName := repoInfo.FullName()
|
||||||
// If endpoint does not support CanonicalName, use the RemoteName instead
|
// If endpoint does not support CanonicalName, use the RemoteName instead
|
||||||
if endpoint.TrimHostname {
|
if endpoint.TrimHostname {
|
||||||
|
@ -67,32 +80,34 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
|
||||||
endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
|
endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
|
||||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
resp, err := pingClient.Do(req)
|
resp, err := pingClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
versions := auth.APIVersions(resp, endpoint.VersionHeader)
|
v2Version := auth.APIVersion{
|
||||||
if endpoint.VersionHeader != "" && len(endpoint.Versions) > 0 {
|
Type: "registry",
|
||||||
var foundVersion bool
|
Version: "2.0",
|
||||||
for _, version := range endpoint.Versions {
|
}
|
||||||
for _, pingVersion := range versions {
|
|
||||||
if version == pingVersion {
|
versions := auth.APIVersions(resp, registry.DefaultRegistryVersionHeader)
|
||||||
foundVersion = true
|
for _, pingVersion := range versions {
|
||||||
}
|
if pingVersion == v2Version {
|
||||||
}
|
// The version header indicates we're definitely
|
||||||
}
|
// talking to a v2 registry. So don't allow future
|
||||||
if !foundVersion {
|
// fallbacks to the v1 protocol.
|
||||||
return nil, errors.New("endpoint does not support v2 API")
|
|
||||||
|
foundVersion = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeManager := auth.NewSimpleChallengeManager()
|
challengeManager := auth.NewSimpleChallengeManager()
|
||||||
if err := challengeManager.AddResponse(resp); err != nil {
|
if err := challengeManager.AddResponse(resp); err != nil {
|
||||||
return nil, err
|
return nil, foundVersion, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if authConfig.RegistryToken != "" {
|
if authConfig.RegistryToken != "" {
|
||||||
|
@ -106,7 +121,8 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
|
||||||
}
|
}
|
||||||
tr := transport.NewTransport(base, modifiers...)
|
tr := transport.NewTransport(base, modifiers...)
|
||||||
|
|
||||||
return client.NewRepository(ctx, repoName, endpoint.URL, tr)
|
repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr)
|
||||||
|
return repo, foundVersion, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) {
|
func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) {
|
||||||
|
@ -147,7 +163,8 @@ func retryOnError(err error) error {
|
||||||
case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
|
case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
|
||||||
return xfer.DoNotRetry{Err: err}
|
return xfer.DoNotRetry{Err: err}
|
||||||
}
|
}
|
||||||
|
case *client.UnexpectedHTTPResponseError:
|
||||||
|
return xfer.DoNotRetry{Err: err}
|
||||||
}
|
}
|
||||||
// let's be nice and fallback if the error is a completely
|
// let's be nice and fallback if the error is a completely
|
||||||
// unexpected one.
|
// unexpected one.
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
@ -49,12 +48,6 @@ func TestTokenPassThru(t *testing.T) {
|
||||||
TrimHostname: false,
|
TrimHostname: false,
|
||||||
TLSConfig: nil,
|
TLSConfig: nil,
|
||||||
//VersionHeader: "verheader",
|
//VersionHeader: "verheader",
|
||||||
Versions: []auth.APIVersion{
|
|
||||||
{
|
|
||||||
Type: "registry",
|
|
||||||
Version: "2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
n, _ := reference.ParseNamed("testremotename")
|
n, _ := reference.ParseNamed("testremotename")
|
||||||
repoInfo := ®istry.RepositoryInfo{
|
repoInfo := ®istry.RepositoryInfo{
|
||||||
|
@ -76,7 +69,8 @@ func TestTokenPassThru(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
p := puller.(*v2Puller)
|
p := puller.(*v2Puller)
|
||||||
p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
ctx := context.Background()
|
||||||
|
p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +78,7 @@ func TestTokenPassThru(t *testing.T) {
|
||||||
logrus.Debug("About to pull")
|
logrus.Debug("About to pull")
|
||||||
// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
|
// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
|
||||||
tag, _ := reference.WithTag(n, "tag_goes_here")
|
tag, _ := reference.WithTag(n, "tag_goes_here")
|
||||||
_ = p.pullV2Repository(context.Background(), tag)
|
_ = p.pullV2Repository(ctx, tag)
|
||||||
|
|
||||||
if !gotToken {
|
if !gotToken {
|
||||||
t.Fatal("Failed to receive registry token")
|
t.Fatal("Failed to receive registry token")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -54,7 +53,8 @@ func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
|
||||||
} {
|
} {
|
||||||
out, err := s.CmdWithError("pull", e.Alias)
|
out, err := s.CmdWithError("pull", e.Alias)
|
||||||
c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
|
c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
|
// Hub returns 401 rather than 404 for nonexistent library/ repos.
|
||||||
|
c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized", check.Commentf("expected unauthorized error message"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
@ -121,14 +120,12 @@ func (s *Service) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
|
||||||
|
|
||||||
// APIEndpoint represents a remote API endpoint
|
// APIEndpoint represents a remote API endpoint
|
||||||
type APIEndpoint struct {
|
type APIEndpoint struct {
|
||||||
Mirror bool
|
Mirror bool
|
||||||
URL string
|
URL string
|
||||||
Version APIVersion
|
Version APIVersion
|
||||||
Official bool
|
Official bool
|
||||||
TrimHostname bool
|
TrimHostname bool
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
VersionHeader string
|
|
||||||
Versions []auth.APIVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
|
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
|
||||||
"github.com/docker/docker/pkg/tlsconfig"
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
)
|
)
|
||||||
|
@ -52,20 +51,12 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v2Versions := []auth.APIVersion{
|
|
||||||
{
|
|
||||||
Type: "registry",
|
|
||||||
Version: "2.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
endpoints = []APIEndpoint{
|
endpoints = []APIEndpoint{
|
||||||
{
|
{
|
||||||
URL: "https://" + hostname,
|
URL: "https://" + hostname,
|
||||||
Version: APIVersion2,
|
Version: APIVersion2,
|
||||||
TrimHostname: true,
|
TrimHostname: true,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
VersionHeader: DefaultRegistryVersionHeader,
|
|
||||||
Versions: v2Versions,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +66,7 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
|
||||||
Version: APIVersion2,
|
Version: APIVersion2,
|
||||||
TrimHostname: true,
|
TrimHostname: true,
|
||||||
// used to check if supposed to be secure via InsecureSkipVerify
|
// used to check if supposed to be secure via InsecureSkipVerify
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
VersionHeader: DefaultRegistryVersionHeader,
|
|
||||||
Versions: v2Versions,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue