github.com/moby/docker@v26.1.3+incompatible/api/server/router/distribution/distribution_routes.go (about)

     1  package distribution // import "github.com/docker/docker/api/server/router/distribution"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net/http"
     7  	"os"
     8  
     9  	"github.com/distribution/reference"
    10  	"github.com/docker/distribution"
    11  	"github.com/docker/distribution/manifest/manifestlist"
    12  	"github.com/docker/distribution/manifest/schema1"
    13  	"github.com/docker/distribution/manifest/schema2"
    14  	"github.com/docker/docker/api/server/httputils"
    15  	"github.com/docker/docker/api/types/registry"
    16  	distributionpkg "github.com/docker/docker/distribution"
    17  	"github.com/docker/docker/errdefs"
    18  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  func (s *distributionRouter) getDistributionInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    23  	if err := httputils.ParseForm(r); err != nil {
    24  		return err
    25  	}
    26  
    27  	w.Header().Set("Content-Type", "application/json")
    28  
    29  	imgName := vars["name"]
    30  
    31  	// TODO why is reference.ParseAnyReference() / reference.ParseNormalizedNamed() not using the reference.ErrTagInvalidFormat (and so on) errors?
    32  	ref, err := reference.ParseAnyReference(imgName)
    33  	if err != nil {
    34  		return errdefs.InvalidParameter(err)
    35  	}
    36  	namedRef, ok := ref.(reference.Named)
    37  	if !ok {
    38  		if _, ok := ref.(reference.Digested); ok {
    39  			// full image ID
    40  			return errors.Errorf("no manifest found for full image ID")
    41  		}
    42  		return errdefs.InvalidParameter(errors.Errorf("unknown image reference format: %s", imgName))
    43  	}
    44  
    45  	// For a search it is not an error if no auth was given. Ignore invalid
    46  	// AuthConfig to increase compatibility with the existing API.
    47  	authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
    48  	repos, err := s.backend.GetRepositories(ctx, namedRef, authConfig)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	// Fetch the manifest; if a mirror is configured, try the mirror first,
    54  	// but continue with upstream on failure.
    55  	//
    56  	// FIXME(thaJeztah): construct "repositories" on-demand;
    57  	// GetRepositories() will attempt to connect to all endpoints (registries),
    58  	// but we may only need the first one if it contains the manifest we're
    59  	// looking for, or if the configured mirror is a pull-through mirror.
    60  	//
    61  	// Logic for this could be implemented similar to "distribution.Pull()",
    62  	// which uses the "pullEndpoints" utility to iterate over the list
    63  	// of endpoints;
    64  	//
    65  	// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L17-L31
    66  	// - https://github.com/moby/moby/blob/12c7411b6b7314bef130cd59f1c7384a7db06d0b/distribution/pull.go#L76-L152
    67  	var lastErr error
    68  	for _, repo := range repos {
    69  		distributionInspect, err := s.fetchManifest(ctx, repo, namedRef)
    70  		if err != nil {
    71  			lastErr = err
    72  			continue
    73  		}
    74  		return httputils.WriteJSON(w, http.StatusOK, distributionInspect)
    75  	}
    76  	return lastErr
    77  }
    78  
    79  func (s *distributionRouter) fetchManifest(ctx context.Context, distrepo distribution.Repository, namedRef reference.Named) (registry.DistributionInspect, error) {
    80  	var distributionInspect registry.DistributionInspect
    81  	if canonicalRef, ok := namedRef.(reference.Canonical); !ok {
    82  		namedRef = reference.TagNameOnly(namedRef)
    83  
    84  		taggedRef, ok := namedRef.(reference.NamedTagged)
    85  		if !ok {
    86  			return registry.DistributionInspect{}, errdefs.InvalidParameter(errors.Errorf("image reference not tagged: %s", namedRef))
    87  		}
    88  
    89  		descriptor, err := distrepo.Tags(ctx).Get(ctx, taggedRef.Tag())
    90  		if err != nil {
    91  			return registry.DistributionInspect{}, err
    92  		}
    93  		distributionInspect.Descriptor = ocispec.Descriptor{
    94  			MediaType: descriptor.MediaType,
    95  			Digest:    descriptor.Digest,
    96  			Size:      descriptor.Size,
    97  		}
    98  	} else {
    99  		// TODO(nishanttotla): Once manifests can be looked up as a blob, the
   100  		// descriptor should be set using blobsrvc.Stat(ctx, canonicalRef.Digest())
   101  		// instead of having to manually fill in the fields
   102  		distributionInspect.Descriptor.Digest = canonicalRef.Digest()
   103  	}
   104  
   105  	// we have a digest, so we can retrieve the manifest
   106  	mnfstsrvc, err := distrepo.Manifests(ctx)
   107  	if err != nil {
   108  		return registry.DistributionInspect{}, err
   109  	}
   110  	mnfst, err := mnfstsrvc.Get(ctx, distributionInspect.Descriptor.Digest)
   111  	if err != nil {
   112  		switch err {
   113  		case reference.ErrReferenceInvalidFormat,
   114  			reference.ErrTagInvalidFormat,
   115  			reference.ErrDigestInvalidFormat,
   116  			reference.ErrNameContainsUppercase,
   117  			reference.ErrNameEmpty,
   118  			reference.ErrNameTooLong,
   119  			reference.ErrNameNotCanonical:
   120  			return registry.DistributionInspect{}, errdefs.InvalidParameter(err)
   121  		}
   122  		return registry.DistributionInspect{}, err
   123  	}
   124  
   125  	mediaType, payload, err := mnfst.Payload()
   126  	if err != nil {
   127  		return registry.DistributionInspect{}, err
   128  	}
   129  	// update MediaType because registry might return something incorrect
   130  	distributionInspect.Descriptor.MediaType = mediaType
   131  	if distributionInspect.Descriptor.Size == 0 {
   132  		distributionInspect.Descriptor.Size = int64(len(payload))
   133  	}
   134  
   135  	// retrieve platform information depending on the type of manifest
   136  	switch mnfstObj := mnfst.(type) {
   137  	case *manifestlist.DeserializedManifestList:
   138  		for _, m := range mnfstObj.Manifests {
   139  			distributionInspect.Platforms = append(distributionInspect.Platforms, ocispec.Platform{
   140  				Architecture: m.Platform.Architecture,
   141  				OS:           m.Platform.OS,
   142  				OSVersion:    m.Platform.OSVersion,
   143  				OSFeatures:   m.Platform.OSFeatures,
   144  				Variant:      m.Platform.Variant,
   145  			})
   146  		}
   147  	case *schema2.DeserializedManifest:
   148  		blobStore := distrepo.Blobs(ctx)
   149  		configJSON, err := blobStore.Get(ctx, mnfstObj.Config.Digest)
   150  		var platform ocispec.Platform
   151  		if err == nil {
   152  			err := json.Unmarshal(configJSON, &platform)
   153  			if err == nil && (platform.OS != "" || platform.Architecture != "") {
   154  				distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
   155  			}
   156  		}
   157  	case *schema1.SignedManifest:
   158  		if os.Getenv("DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE") == "" {
   159  			return registry.DistributionInspect{}, distributionpkg.DeprecatedSchema1ImageError(namedRef)
   160  		}
   161  		platform := ocispec.Platform{
   162  			Architecture: mnfstObj.Architecture,
   163  			OS:           "linux",
   164  		}
   165  		distributionInspect.Platforms = append(distributionInspect.Platforms, platform)
   166  	}
   167  	return distributionInspect, nil
   168  }