Skip to content

Commit

Permalink
Merge pull request moby#47023 from thaJeztah/fix_distribution_mirrors
Browse files Browse the repository at this point in the history
api: fix "GET /distribution" endpoint ignoring mirrors
  • Loading branch information
thaJeztah authored Jan 4, 2024
2 parents e904238 + 8aacbb3 commit 9cebefa
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 16 deletions.
2 changes: 1 addition & 1 deletion api/server/router/distribution/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
49 changes: 38 additions & 11 deletions api/server/router/distribution/distribution_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
}
1 change: 1 addition & 0 deletions daemon/cluster/executor/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
16 changes: 16 additions & 0 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
})
}
41 changes: 37 additions & 4 deletions distribution/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,33 @@ package distribution
import (
"context"

"github.com/containerd/log"
"github.com/distribution/reference"
"github.com/docker/distribution"
"github.com/docker/docker/errdefs"
)

// 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)
Expand All @@ -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
}
}
if len(repositories) == 0 {
return nil, lastError
}
return repository, lastError
return repositories, nil
}

0 comments on commit 9cebefa

Please sign in to comment.