Merge pull request #47023 from thaJeztah/fix_distribution_mirrors
api: fix "GET /distribution" endpoint ignoring mirrors
This commit is contained in:
commit
9cebefa717
5 changed files with 93 additions and 16 deletions
|
@ -11,5 +11,5 @@ import (
|
|||
// Backend is all the methods that need to be implemented
|
||||
// to provide image specific functionality.
|
||||
type Backend interface {
|
||||
GetRepository(context.Context, reference.Named, *registry.AuthConfig) (distribution.Repository, error)
|
||||
GetRepositories(context.Context, reference.Named, *registry.AuthConfig) ([]distribution.Repository, error)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
|
@ -42,24 +43,50 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
|||
// For a search it is not an error if no auth was given. Ignore invalid
|
||||
// AuthConfig to increase compatibility with the existing API.
|
||||
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
|
||||
distrepo, err := s.backend.GetRepository(ctx, namedRef, authConfig)
|
||||
repos, err := s.backend.GetRepositories(ctx, namedRef, authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blobsrvc := distrepo.Blobs(ctx)
|
||||
|
||||
// Fetch the manifest; if a mirror is configured, try the mirror first,
|
||||
// but continue with upstream on failure.
|
||||
//
|
||||
// FIXME(thaJeztah): construct "repositories" on-demand;
|
||||
// GetRepositories() will attempt to connect to all endpoints (registries),
|
||||
// but we may only need the first one if it contains the manifest we're
|
||||
// looking for, or if the configured mirror is a pull-through mirror.
|
||||
//
|
||||
// Logic for this could be implemented similar to "distribution.Pull()",
|
||||
// which uses the "pullEndpoints" utility to iterate over the list
|
||||
// of endpoints;
|
||||
//
|
||||
// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L17-L31
|
||||
// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L76-L152
|
||||
var lastErr error
|
||||
for _, repo := range repos {
|
||||
distributionInspect, err := s.fetchManifest(ctx, repo, namedRef)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return httputils.WriteJSON(w, http.StatusOK, distributionInspect)
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (s *distributionRouter) fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
|
||||
var distributionInspect registry.DistributionInspect
|
||||
if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
|
||||
namedRef = reference.TagNameOnly(namedRef)
|
||||
|
||||
taggedRef, ok := namedRef.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return errdefs.InvalidParameter(errors.Errorf("image reference not tagged: %s", image))
|
||||
return registry.DistributionInspect{}, errdefs.InvalidParameter(errors.Errorf("image reference not tagged: %s", namedRef))
|
||||
}
|
||||
|
||||
descriptor, err := distrepo.Tags(ctx).Get(ctx, taggedRef.Tag())
|
||||
if err != nil {
|
||||
return err
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
distributionInspect.Descriptor = ocispec.Descriptor{
|
||||
MediaType: descriptor.MediaType,
|
||||
|
@ -76,7 +103,7 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
|||
// we have a digest, so we can retrieve the manifest
|
||||
mnfstsrvc, err := distrepo.Manifests(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
mnfst, err := mnfstsrvc.Get(ctx, distributionInspect.Descriptor.Digest)
|
||||
if err != nil {
|
||||
|
@ -88,14 +115,14 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
|||
reference.ErrNameEmpty,
|
||||
reference.ErrNameTooLong,
|
||||
reference.ErrNameNotCanonical:
|
||||
return errdefs.InvalidParameter(err)
|
||||
return registry.DistributionInspect{}, errdefs.InvalidParameter(err)
|
||||
}
|
||||
return err
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
|
||||
mediaType, payload, err := mnfst.Payload()
|
||||
if err != nil {
|
||||
return err
|
||||
return registry.DistributionInspect{}, err
|
||||
}
|
||||
// update MediaType because registry might return something incorrect
|
||||
distributionInspect.Descriptor.MediaType = mediaType
|
||||
|
@ -116,7 +143,8 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
|||
})
|
||||
}
|
||||
case *schema2.DeserializedManifest:
|
||||
configJSON, err := blobsrvc.Get(ctx, mnfstObj.Config.Digest)
|
||||
blobStore := distrepo.Blobs(ctx)
|
||||
configJSON, err := blobStore.Get(ctx, mnfstObj.Config.Digest)
|
||||
var platform ocispec.Platform
|
||||
if err == nil {
|
||||
err := json.Unmarshal(configJSON, &platform)
|
||||
|
@ -131,6 +159,5 @@ func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.Res
|
|||
}
|
||||
distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
|
||||
}
|
||||
|
||||
return httputils.WriteJSON(w, http.StatusOK, distributionInspect)
|
||||
return distributionInspect, nil
|
||||
}
|
||||
|
|
|
@ -78,5 +78,6 @@ type VolumeBackend interface {
|
|||
type ImageBackend interface {
|
||||
PullImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error
|
||||
GetRepository(context.Context, reference.Named, *registry.AuthConfig) (distribution.Repository, error)
|
||||
GetRepositories(context.Context, reference.Named, *registry.AuthConfig) ([]distribution.Repository, error)
|
||||
GetImage(ctx context.Context, refOrID string, options opts.GetImageOpts) (*image.Image, error)
|
||||
}
|
||||
|
|
|
@ -1595,3 +1595,19 @@ func (i *imageBackend) GetRepository(ctx context.Context, ref reference.Named, a
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetRepositories returns a list of repositories configured for the given
|
||||
// reference. Multiple repositories can be returned if the reference is for
|
||||
// the default (Docker Hub) registry and a mirror is configured, but it omits
|
||||
// registries that were not reachable (pinging the /v2/ endpoint failed).
|
||||
//
|
||||
// It returns an error if it was unable to reach any of the registries for
|
||||
// the given reference, or if the provided reference is invalid.
|
||||
func (i *imageBackend) GetRepositories(ctx context.Context, ref reference.Named, authConfig *registrytypes.AuthConfig) ([]dist.Repository, error) {
|
||||
return distribution.GetRepositories(ctx, ref, &distribution.ImagePullConfig{
|
||||
Config: distribution.Config{
|
||||
AuthConfig: authConfig,
|
||||
RegistryService: i.registryService,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package distribution
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/errdefs"
|
||||
|
@ -10,6 +11,25 @@ import (
|
|||
|
||||
// GetRepository returns a repository from the registry.
|
||||
func GetRepository(ctx context.Context, ref reference.Named, config *ImagePullConfig) (repository distribution.Repository, lastError error) {
|
||||
repos, err := getRepositories(ctx, ref, config, true)
|
||||
if len(repos) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return repos[0], nil
|
||||
}
|
||||
|
||||
// GetRepositories returns a list of repositories configured for the given
|
||||
// reference. Multiple repositories can be returned if the reference is for
|
||||
// the default (Docker Hub) registry and a mirror is configured, but it omits
|
||||
// registries that were not reachable (pinging the /v2/ endpoint failed).
|
||||
//
|
||||
// It returns an error if it was unable to reach any of the registries for
|
||||
// the given reference, or if the provided reference is invalid.
|
||||
func GetRepositories(ctx context.Context, ref reference.Named, config *ImagePullConfig) ([]distribution.Repository, error) {
|
||||
return getRepositories(ctx, ref, config, false)
|
||||
}
|
||||
|
||||
func getRepositories(ctx context.Context, ref reference.Named, config *ImagePullConfig, firstOnly bool) ([]distribution.Repository, error) {
|
||||
repoInfo, err := config.RegistryService.ResolveRepository(ref)
|
||||
if err != nil {
|
||||
return nil, errdefs.InvalidParameter(err)
|
||||
|
@ -24,11 +44,24 @@ func GetRepository(ctx context.Context, ref reference.Named, config *ImagePullCo
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
repositories []distribution.Repository
|
||||
lastError error
|
||||
)
|
||||
for _, endpoint := range endpoints {
|
||||
repository, lastError = newRepository(ctx, repoInfo, endpoint, nil, config.AuthConfig, "pull")
|
||||
if lastError == nil {
|
||||
break
|
||||
repo, err := newRepository(ctx, repoInfo, endpoint, nil, config.AuthConfig, "pull")
|
||||
if err != nil {
|
||||
log.G(ctx).WithFields(log.Fields{"endpoint": endpoint.URL.String(), "error": err}).Info("endpoint")
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
repositories = append(repositories, repo)
|
||||
if firstOnly {
|
||||
return repositories, nil
|
||||
}
|
||||
}
|
||||
return repository, lastError
|
||||
if len(repositories) == 0 {
|
||||
return nil, lastError
|
||||
}
|
||||
return repositories, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue