Merge pull request #41757 from thaJeztah/carry_39371_remove_more_v1_code

This commit is contained in:
Brian Goff 2021-03-18 11:38:07 -07:00 committed by GitHub
commit ece4cd4c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 57 additions and 221 deletions

View File

@ -11,5 +11,5 @@ import (
// Backend is all the methods that need to be implemented // Backend is all the methods that need to be implemented
// to provide image specific functionality. // to provide image specific functionality.
type Backend interface { type Backend interface {
GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error) GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, error)
} }

View File

@ -57,7 +57,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
return errdefs.InvalidParameter(errors.Errorf("unknown image reference format: %s", image)) return errdefs.InvalidParameter(errors.Errorf("unknown image reference format: %s", image))
} }
distrepo, _, err := s.backend.GetRepository(ctx, namedRef, config) distrepo, err := s.backend.GetRepository(ctx, namedRef, config)
if err != nil { if err != nil {
return err return err
} }

View File

@ -72,6 +72,6 @@ type VolumeBackend interface {
// ImageBackend is used by an executor to perform image operations // ImageBackend is used by an executor to perform image operations
type ImageBackend interface { type ImageBackend interface {
PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, bool, error) GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, error)
LookupImage(name string) (*types.ImageInspect, error) LookupImage(name string) (*types.ImageInspect, error)
} }

View File

@ -633,7 +633,7 @@ func (c *Cluster) imageWithDigestString(ctx context.Context, image string, authC
return "", errors.Errorf("image reference not tagged: %s", image) return "", errors.Errorf("image reference not tagged: %s", image)
} }
repo, _, err := c.config.ImageBackend.GetRepository(ctx, taggedRef, authConfig) repo, err := c.config.ImageBackend.GetRepository(ctx, taggedRef, authConfig)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -15,7 +15,6 @@ import (
progressutils "github.com/docker/docker/distribution/utils" progressutils "github.com/docker/docker/distribution/utils"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/progress"
"github.com/docker/docker/registry"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -110,41 +109,36 @@ func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference
} }
// GetRepository returns a repository from the registry. // GetRepository returns a repository from the registry.
func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, authConfig *types.AuthConfig) (dist.Repository, bool, error) { func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, authConfig *types.AuthConfig) (dist.Repository, error) {
// get repository info // get repository info
repoInfo, err := i.registryService.ResolveRepository(ref) repoInfo, err := i.registryService.ResolveRepository(ref)
if err != nil { if err != nil {
return nil, false, errdefs.InvalidParameter(err) return nil, errdefs.InvalidParameter(err)
} }
// makes sure name is not empty or `scratch` // makes sure name is not empty or `scratch`
if err := distribution.ValidateRepoName(repoInfo.Name); err != nil { if err := distribution.ValidateRepoName(repoInfo.Name); err != nil {
return nil, false, errdefs.InvalidParameter(err) return nil, errdefs.InvalidParameter(err)
} }
// get endpoints // get endpoints
endpoints, err := i.registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) endpoints, err := i.registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
// retrieve repository // retrieve repository
var ( var (
confirmedV2 bool
repository dist.Repository repository dist.Repository
lastError error lastError error
) )
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if endpoint.Version == registry.APIVersion1 { repository, lastError = distribution.NewV2Repository(ctx, repoInfo, endpoint, nil, authConfig, "pull")
continue if lastError == nil {
}
repository, confirmedV2, lastError = distribution.NewV2Repository(ctx, repoInfo, endpoint, nil, authConfig, "pull")
if lastError == nil && confirmedV2 {
break break
} }
} }
return repository, confirmedV2, lastError return repository, lastError
} }
func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) { func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) {

View File

@ -34,10 +34,6 @@ func (e ErrNoSupport) Error() string {
type fallbackError struct { type fallbackError struct {
// err is the error being wrapped. // err is the error being wrapped.
err error 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
// transportOK is set to true if we managed to speak HTTP with the // transportOK is set to true if we managed to speak HTTP with the
// registry. This confirms that we're using appropriate TLS settings // registry. This confirms that we're using appropriate TLS settings
// (or lack of TLS). // (or lack of TLS).
@ -53,15 +49,6 @@ func (f fallbackError) Cause() error {
return f.err return f.err
} }
// shouldV2Fallback returns true if this error is a reason to fall back to v1.
func shouldV2Fallback(err errcode.Error) bool {
switch err.Code {
case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
return true
}
return false
}
type notFoundError struct { type notFoundError struct {
cause errcode.Error cause errcode.Error
ref reference.Named ref reference.Named
@ -141,7 +128,7 @@ func continueOnError(err error, mirrorEndpoint bool) bool {
case ErrNoSupport: case ErrNoSupport:
return continueOnError(v.Err, mirrorEndpoint) return continueOnError(v.Err, mirrorEndpoint)
case errcode.Error: case errcode.Error:
return mirrorEndpoint || shouldV2Fallback(v) return mirrorEndpoint
case *client.UnexpectedHTTPResponseError: case *client.UnexpectedHTTPResponseError:
return true return true
case ImageConfigPullError: case ImageConfigPullError:

View File

@ -7,26 +7,26 @@ import (
"testing" "testing"
"github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/errcode"
v2 "github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/client" "github.com/docker/distribution/registry/client"
) )
var errUnexpected = errors.New("some totally unexpected error")
var alwaysContinue = []error{ var alwaysContinue = []error{
&client.UnexpectedHTTPResponseError{}, &client.UnexpectedHTTPResponseError{},
errcode.Errors{},
// Some errcode.Errors that don't disprove the existence of a V1 image errUnexpected,
errcode.Error{Code: errcode.ErrorCodeUnauthorized}, // nested
errcode.Error{Code: v2.ErrorCodeManifestUnknown}, errcode.Errors{errUnexpected},
errcode.Error{Code: v2.ErrorCodeNameUnknown}, ErrNoSupport{Err: errUnexpected},
errors.New("some totally unexpected error"),
} }
var continueFromMirrorEndpoint = []error{ var continueFromMirrorEndpoint = []error{
ImageConfigPullError{}, ImageConfigPullError{},
errcode.Error{},
// Some other errcode.Error that doesn't indicate we should search for a V1 image. // nested
errcode.Error{Code: errcode.ErrorCodeTooManyRequests}, errcode.Errors{errcode.Error{}},
ErrNoSupport{Err: errcode.Error{}},
} }
var neverContinue = []error{ var neverContinue = []error{
@ -67,19 +67,3 @@ func TestContinueOnError_NeverContinue(t *testing.T) {
} }
} }
} }
func TestContinueOnError_UnnestsErrors(t *testing.T) {
// ContinueOnError should evaluate nested errcode.Errors.
// Assumes that v2.ErrorCodeNameUnknown is a continueable error code.
err := errcode.Errors{errcode.Error{Code: v2.ErrorCodeNameUnknown}}
if !continueOnError(err, false) {
t.Fatal("ContinueOnError should unnest, base return value on errcode.Errors")
}
// Assumes that errcode.ErrorCodeTooManyRequests is not a V1-fallback indication
err = errcode.Errors{errcode.Error{Code: errcode.ErrorCodeTooManyRequests}}
if continueOnError(err, false) {
t.Fatal("ContinueOnError should unnest, base return value on errcode.Errors")
}
}

View File

@ -24,11 +24,7 @@ type Puller interface {
Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) error Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) error
} }
// newPuller returns a Puller interface that will pull from either a v1 or v2 // newPuller returns a Puller interface that will pull from a v2 registry.
// registry. The endpoint argument contains a Version field that determines
// whether a v1 or v2 puller will be created. The other parameters are passed
// through to the underlying puller implementation for use during the actual
// pull operation.
func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, local ContentStore) (Puller, error) { func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, local ContentStore) (Puller, error) {
switch endpoint.Version { switch endpoint.Version {
case registry.APIVersion2: case registry.APIVersion2:
@ -78,26 +74,12 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
// 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
// confirmedTLSRegistries is a map indicating which registries // confirmedTLSRegistries is a map indicating which registries
// are known to be using TLS. There should never be a plaintext // are known to be using TLS. There should never be a plaintext
// retry for any of these. // retry for any of these.
confirmedTLSRegistries = make(map[string]struct{}) confirmedTLSRegistries = make(map[string]struct{})
) )
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if imagePullConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 {
continue
}
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
continue
}
if endpoint.URL.Scheme != "https" { if endpoint.URL.Scheme != "https" {
if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
@ -122,7 +104,6 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
default: default:
if fallbackErr, ok := err.(fallbackError); ok { if fallbackErr, ok := err.(fallbackError); ok {
fallback = true fallback = true
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -19,8 +18,6 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer" "github.com/docker/docker/distribution/xfer"
@ -61,15 +58,12 @@ 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
manifestStore *manifestStore manifestStore *manifestStore
} }
func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) { func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) (err error) {
// TODO(tiborvass): was ReceiveTimeout // TODO(tiborvass): was ReceiveTimeout
p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") p.repo, 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 err return err
@ -87,7 +81,6 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform *spec
if continueOnError(err, p.endpoint.Mirror) { if continueOnError(err, p.endpoint.Mirror) {
return fallbackError{ return fallbackError{
err: err, err: err,
confirmedV2: p.confirmedV2,
transportOK: true, transportOK: true,
} }
} }
@ -105,16 +98,9 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, pl
} else { } else {
tags, err := p.repo.Tags(ctx).All(ctx) tags, err := p.repo.Tags(ctx).All(ctx)
if err != nil { if err != nil {
// If this repository doesn't exist on V2, we should return err
// permit a fallback to V1.
return allowV1Fallback(err)
} }
// The v2 registry knows about this repository, so we will not
// allow fallback to the v1 protocol even if we encounter an
// error later on.
p.confirmedV2 = true
for _, tag := range tags { for _, tag := range tags {
tagRef, err := reference.WithTag(ref, tag) tagRef, err := reference.WithTag(ref, tag)
if err != nil { if err != nil {
@ -353,7 +339,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
tagService := p.repo.Tags(ctx) tagService := p.repo.Tags(ctx)
desc, err := tagService.Get(ctx, tagged.Tag()) desc, err := tagService.Get(ctx, tagged.Tag())
if err != nil { if err != nil {
return false, allowV1Fallback(err) return false, err
} }
dgst = desc.Digest dgst = desc.Digest
@ -427,10 +413,6 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
} }
} }
// If manSvc.Get succeeded, we can be confident that the registry on
// the other side speaks the v2 protocol.
p.confirmedV2 = true
logrus.Debugf("Pulling ref from V2 registry: %s", reference.FamiliarString(ref)) logrus.Debugf("Pulling ref from V2 registry: %s", reference.FamiliarString(ref))
progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named())) progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+reference.FamiliarName(p.repo.Named()))
@ -945,39 +927,6 @@ func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (dig
return digest.FromBytes(canonical), nil return digest.FromBytes(canonical), nil
} }
// allowV1Fallback checks if the error is a possible reason to fallback to v1
// (even if confirmedV2 has been set already), and if so, wraps the error in
// a fallbackError with confirmedV2 set to false. Otherwise, it returns the
// error unmodified.
func allowV1Fallback(err error) error {
switch v := err.(type) {
case errcode.Errors:
if len(v) != 0 {
if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) {
return fallbackError{
err: err,
confirmedV2: false,
transportOK: true,
}
}
}
case errcode.Error:
if shouldV2Fallback(v) {
return fallbackError{
err: err,
confirmedV2: false,
transportOK: true,
}
}
case *url.Error:
if v.Err == auth.ErrNoBasicAuthCredentials {
return fallbackError{err: err, confirmedV2: false}
}
}
return err
}
func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Reference) (m *schema1.Manifest, err error) { func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Reference) (m *schema1.Manifest, err error) {
// If pull by digest, then verify the manifest digest. NOTE: It is // If pull by digest, then verify the manifest digest. NOTE: It is
// important to do this first, before any other content validation. If the // important to do this first, before any other content validation. If the

View File

@ -73,11 +73,6 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
var ( var (
lastErr error 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
// confirmedTLSRegistries is a map indicating which registries // confirmedTLSRegistries is a map indicating which registries
// are known to be using TLS. There should never be a plaintext // are known to be using TLS. There should never be a plaintext
// retry for any of these. // retry for any of these.
@ -85,14 +80,6 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
) )
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if imagePushConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 {
continue
}
if confirmedV2 && endpoint.Version == registry.APIVersion1 {
logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
continue
}
if endpoint.URL.Scheme != "https" { if endpoint.URL.Scheme != "https" {
if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
@ -114,7 +101,6 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
case <-ctx.Done(): case <-ctx.Done():
default: default:
if fallbackErr, ok := err.(fallbackError); ok { if fallbackErr, ok := err.(fallbackError); ok {
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
} }

View File

@ -53,16 +53,13 @@ type pushState struct {
// involve the same layers. It is also used to fill in digest and size // involve the same layers. It is also used to fill in digest and size
// information when building the manifest. // information when building the manifest.
remoteLayers map[layer.DiffID]distribution.Descriptor remoteLayers map[layer.DiffID]distribution.Descriptor
// 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
hasAuthInfo bool hasAuthInfo bool
} }
func (p *v2Pusher) Push(ctx context.Context) (err error) { func (p *v2Pusher) Push(ctx context.Context) (err error) {
p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor) p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor)
p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull") p.repo, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
p.pushState.hasAuthInfo = p.config.AuthConfig.RegistryToken != "" || (p.config.AuthConfig.Username != "" && p.config.AuthConfig.Password != "") p.pushState.hasAuthInfo = p.config.AuthConfig.RegistryToken != "" || (p.config.AuthConfig.Username != "" && p.config.AuthConfig.Password != "")
if err != nil { if err != nil {
logrus.Debugf("Error getting v2 registry: %v", err) logrus.Debugf("Error getting v2 registry: %v", err)
@ -73,7 +70,6 @@ func (p *v2Pusher) Push(ctx context.Context) (err error) {
if continueOnError(err, p.endpoint.Mirror) { if continueOnError(err, p.endpoint.Mirror) {
return fallbackError{ return fallbackError{
err: err, err: err,
confirmedV2: p.pushState.confirmedV2,
transportOK: true, transportOK: true,
} }
} }
@ -370,7 +366,6 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
err.Descriptor.MediaType = schema2.MediaTypeLayer err.Descriptor.MediaType = schema2.MediaTypeLayer
pd.pushState.Lock() pd.pushState.Lock()
pd.pushState.confirmedV2 = true
pd.pushState.remoteLayers[diffID] = err.Descriptor pd.pushState.remoteLayers[diffID] = err.Descriptor
pd.pushState.Unlock() pd.pushState.Unlock()
@ -510,8 +505,6 @@ func (pd *v2PushDescriptor) uploadUsingSession(
} }
pd.pushState.Lock() pd.pushState.Lock()
// If Commit succeeded, that's an indication that the remote registry speaks the v2 protocol.
pd.pushState.confirmedV2 = true
pd.pushState.remoteLayers[diffID] = desc pd.pushState.remoteLayers[diffID] = desc
pd.pushState.Unlock() pd.pushState.Unlock()

View File

@ -532,7 +532,7 @@ func TestWhenEmptyAuthConfig(t *testing.T) {
Scheme: "https", Scheme: "https",
Host: "index.docker.io", Host: "index.docker.io",
}, },
Version: registry.APIVersion1, Version: registry.APIVersion2,
TrimHostname: true, TrimHostname: true,
}, },
} }

View File

@ -58,7 +58,7 @@ func init() {
func NewV2Repository( func NewV2Repository(
ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint,
metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string,
) (repo distribution.Repository, foundVersion bool, err error) { ) (repo distribution.Repository, err error) {
repoName := repoInfo.Name.Name() repoName := repoInfo.Name.Name()
// 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 {
@ -84,16 +84,15 @@ func NewV2Repository(
modifiers := registry.Headers(dockerversion.DockerUserAgent(ctx), metaHeaders) modifiers := registry.Headers(dockerversion.DockerUserAgent(ctx), metaHeaders)
authTransport := transport.NewTransport(base, modifiers...) authTransport := transport.NewTransport(base, modifiers...)
challengeManager, foundVersion, err := registry.PingV2Registry(endpoint.URL, authTransport) challengeManager, err := registry.PingV2Registry(endpoint.URL, authTransport)
if err != nil { if err != nil {
transportOK := false transportOK := false
if responseErr, ok := err.(registry.PingResponseError); ok { if responseErr, ok := err.(registry.PingResponseError); ok {
transportOK = true transportOK = true
err = responseErr.Err err = responseErr.Err
} }
return nil, foundVersion, fallbackError{ return nil, fallbackError{
err: err, err: err,
confirmedV2: foundVersion,
transportOK: transportOK, transportOK: transportOK,
} }
} }
@ -123,9 +122,8 @@ func NewV2Repository(
repoNameRef, err := reference.WithName(repoName) repoNameRef, err := reference.WithName(repoName)
if err != nil { if err != nil {
return nil, foundVersion, fallbackError{ return nil, fallbackError{
err: err, err: err,
confirmedV2: foundVersion,
transportOK: true, transportOK: true,
} }
} }
@ -134,7 +132,6 @@ func NewV2Repository(
if err != nil { if err != nil {
err = fallbackError{ err = fallbackError{
err: err, err: err,
confirmedV2: foundVersion,
transportOK: true, transportOK: true,
} }
} }

View File

@ -75,7 +75,7 @@ func testTokenPassThru(t *testing.T, ts *httptest.Server) {
} }
p := puller.(*v2Puller) p := puller.(*v2Puller)
ctx := context.Background() ctx := context.Background()
p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") 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)
} }

View File

@ -87,26 +87,19 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
logrus.Debugf("attempting v2 login to registry endpoint %s", endpointStr) logrus.Debugf("attempting v2 login to registry endpoint %s", endpointStr)
loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil) loginClient, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
req, err := http.NewRequest(http.MethodGet, endpointStr, nil) req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
if err != nil { if err != nil {
if !foundV2 {
err = fallbackError{err: err}
}
return "", "", err return "", "", err
} }
resp, err := loginClient.Do(req) resp, err := loginClient.Do(req)
if err != nil { if err != nil {
err = translateV2AuthError(err) err = translateV2AuthError(err)
if !foundV2 {
err = fallbackError{err: err}
}
return "", "", err return "", "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -117,19 +110,13 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string // TODO(dmcgowan): Attempt to further interpret result, status code and error code string
err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
if !foundV2 {
err = fallbackError{err: err}
}
return "", "", err return "", "", err
} }
func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) { func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) challengeManager, err := PingV2Registry(endpoint, authTransport)
if err != nil { if err != nil {
if !foundV2 { return nil, err
err = fallbackError{err: err}
}
return nil, foundV2, err
} }
tokenHandlerOptions := auth.TokenHandlerOptions{ tokenHandlerOptions := auth.TokenHandlerOptions{
@ -147,8 +134,7 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi
return &http.Client{ return &http.Client{
Transport: tr, Transport: tr,
Timeout: 15 * time.Second, Timeout: 15 * time.Second,
}, foundV2, nil }, nil
} }
// ConvertToHostname converts a registry url which has http|https prepended // ConvertToHostname converts a registry url which has http|https prepended
@ -197,18 +183,9 @@ func (err PingResponseError) Error() string {
} }
// PingV2Registry attempts to ping a v2 registry and on success return a // PingV2Registry attempts to ping a v2 registry and on success return a
// challenge manager for the supported authentication types and // challenge manager for the supported authentication types.
// whether v2 was confirmed by the response. If a response is received but // If a response is received but cannot be interpreted, a PingResponseError will be returned.
// cannot be interpreted a PingResponseError will be returned. func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) {
func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, bool, error) {
var (
foundV2 = false
v2Version = auth.APIVersion{
Type: "registry",
Version: "2.0",
}
)
pingClient := &http.Client{ pingClient := &http.Client{
Transport: transport, Transport: transport,
Timeout: 15 * time.Second, Timeout: 15 * time.Second,
@ -216,32 +193,20 @@ func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.M
endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
req, err := http.NewRequest(http.MethodGet, endpointStr, nil) req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
resp, err := pingClient.Do(req) resp, err := pingClient.Do(req)
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
versions := auth.APIVersions(resp, DefaultRegistryVersionHeader)
for _, pingVersion := range versions {
if pingVersion == v2Version {
// The version header indicates we're definitely
// talking to a v2 registry. So don't allow future
// fallbacks to the v1 protocol.
foundV2 = true
break
}
}
challengeManager := challenge.NewSimpleManager() challengeManager := challenge.NewSimpleManager()
if err := challengeManager.AddResponse(resp); err != nil { if err := challengeManager.AddResponse(resp); err != nil {
return nil, foundV2, PingResponseError{ return nil, PingResponseError{
Err: err, Err: err,
} }
} }
return challengeManager, foundV2, nil return challengeManager, nil
} }

View File

@ -22,6 +22,7 @@ type V1Endpoint struct {
} }
// NewV1Endpoint parses the given address to return a registry endpoint. // NewV1Endpoint parses the given address to return a registry endpoint.
// TODO: remove. This is only used by search.
func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
tlsConfig, err := newTLSConfig(index.Name, index.Secure) tlsConfig, err := newTLSConfig(index.Name, index.Secure)
if err != nil { if err != nil {

View File

@ -135,12 +135,11 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
if err == nil { if err == nil {
return return
} }
if fErr, ok := err.(fallbackError); ok { if errdefs.IsUnauthorized(err) {
logrus.WithError(fErr.err).Infof("Error logging in to endpoint, trying next endpoint") // Failed to authenticate; don't continue with (non-TLS) endpoints.
continue return status, token, err
} }
logrus.WithError(err).Infof("Error logging in to endpoint, trying next endpoint")
return "", "", err
} }
return "", "", err return "", "", err
@ -194,14 +193,14 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
} }
modifiers := Headers(userAgent, nil) modifiers := Headers(userAgent, nil)
v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes) v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
if err != nil { if err != nil {
if fErr, ok := err.(fallbackError); ok { if fErr, ok := err.(fallbackError); ok {
logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err) logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
} else { } else {
return nil, err return nil, err
} }
} else if foundV2 { } else {
// Copy non transport http client features // Copy non transport http client features
v2Client.Timeout = endpoint.client.Timeout v2Client.Timeout = endpoint.client.Timeout
v2Client.CheckRedirect = endpoint.client.CheckRedirect v2Client.CheckRedirect = endpoint.client.CheckRedirect