distribution_routes.go 5.8 KB

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