pull_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package image
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "os"
  8. "path"
  9. "strings"
  10. "testing"
  11. "github.com/containerd/containerd"
  12. "github.com/containerd/containerd/content"
  13. "github.com/containerd/containerd/content/local"
  14. "github.com/containerd/containerd/images"
  15. "github.com/containerd/containerd/platforms"
  16. "github.com/docker/docker/api/types"
  17. "github.com/docker/docker/errdefs"
  18. "github.com/docker/docker/testutil/registry"
  19. "github.com/opencontainers/go-digest"
  20. "github.com/opencontainers/image-spec/specs-go"
  21. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  22. "gotest.tools/v3/assert"
  23. is "gotest.tools/v3/assert/cmp"
  24. "gotest.tools/v3/skip"
  25. )
  26. func TestImagePullPlatformInvalid(t *testing.T) {
  27. ctx := setupTest(t)
  28. client := testEnv.APIClient()
  29. _, err := client.ImagePull(ctx, "docker.io/library/hello-world:latest", types.ImagePullOptions{Platform: "foobar"})
  30. assert.Assert(t, err != nil)
  31. assert.ErrorContains(t, err, "unknown operating system or architecture")
  32. assert.Assert(t, errdefs.IsInvalidParameter(err))
  33. }
  34. func createTestImage(ctx context.Context, t testing.TB, store content.Store) ocispec.Descriptor {
  35. w, err := store.Writer(ctx, content.WithRef("layer"))
  36. assert.NilError(t, err)
  37. defer w.Close()
  38. // Empty layer with just a root dir
  39. const layer = `./0000775000000000000000000000000014201045023007702 5ustar rootroot`
  40. _, err = w.Write([]byte(layer))
  41. assert.NilError(t, err)
  42. err = w.Commit(ctx, int64(len(layer)), digest.FromBytes([]byte(layer)))
  43. assert.NilError(t, err)
  44. layerDigest := w.Digest()
  45. w.Close()
  46. img := ocispec.Image{
  47. Platform: platforms.DefaultSpec(),
  48. RootFS: ocispec.RootFS{Type: "layers", DiffIDs: []digest.Digest{layerDigest}},
  49. Config: ocispec.ImageConfig{WorkingDir: "/"},
  50. }
  51. imgJSON, err := json.Marshal(img)
  52. assert.NilError(t, err)
  53. w, err = store.Writer(ctx, content.WithRef("config"))
  54. assert.NilError(t, err)
  55. defer w.Close()
  56. _, err = w.Write(imgJSON)
  57. assert.NilError(t, err)
  58. assert.NilError(t, w.Commit(ctx, int64(len(imgJSON)), digest.FromBytes(imgJSON)))
  59. configDigest := w.Digest()
  60. w.Close()
  61. info, err := store.Info(ctx, layerDigest)
  62. assert.NilError(t, err)
  63. manifest := ocispec.Manifest{
  64. Versioned: specs.Versioned{
  65. SchemaVersion: 2,
  66. },
  67. MediaType: images.MediaTypeDockerSchema2Manifest,
  68. Config: ocispec.Descriptor{
  69. MediaType: images.MediaTypeDockerSchema2Config,
  70. Digest: configDigest,
  71. Size: int64(len(imgJSON)),
  72. },
  73. Layers: []ocispec.Descriptor{{
  74. MediaType: images.MediaTypeDockerSchema2Layer,
  75. Digest: layerDigest,
  76. Size: info.Size,
  77. }},
  78. }
  79. manifestJSON, err := json.Marshal(manifest)
  80. assert.NilError(t, err)
  81. w, err = store.Writer(ctx, content.WithRef("manifest"))
  82. assert.NilError(t, err)
  83. defer w.Close()
  84. _, err = w.Write(manifestJSON)
  85. assert.NilError(t, err)
  86. assert.NilError(t, w.Commit(ctx, int64(len(manifestJSON)), digest.FromBytes(manifestJSON)))
  87. manifestDigest := w.Digest()
  88. w.Close()
  89. return ocispec.Descriptor{
  90. MediaType: images.MediaTypeDockerSchema2Manifest,
  91. Digest: manifestDigest,
  92. Size: int64(len(manifestJSON)),
  93. }
  94. }
  95. // Make sure that pulling by an already cached digest but for a different ref (that should not have that digest)
  96. // verifies with the remote that the digest exists in that repo.
  97. func TestImagePullStoredfDigestForOtherRepo(t *testing.T) {
  98. skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
  99. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "We don't run a test registry on Windows")
  100. skip.If(t, testEnv.IsRootless, "Rootless has a different view of localhost (needed for test registry access)")
  101. ctx := setupTest(t)
  102. reg := registry.NewV2(t, registry.WithStdout(os.Stdout), registry.WithStderr(os.Stderr))
  103. defer reg.Close()
  104. reg.WaitReady(t)
  105. // First create an image and upload it to our local registry
  106. // Then we'll download it so that we can make sure the content is available in dockerd's manifest cache.
  107. // Then we'll try to pull the same digest but with a different repo name.
  108. dir := t.TempDir()
  109. store, err := local.NewStore(dir)
  110. assert.NilError(t, err)
  111. desc := createTestImage(ctx, t, store)
  112. remote := path.Join(registry.DefaultURL, "test:latest")
  113. c8dClient, err := containerd.New("", containerd.WithServices(containerd.WithContentStore(store)))
  114. assert.NilError(t, err)
  115. c8dClient.Push(ctx, remote, desc)
  116. assert.NilError(t, err)
  117. client := testEnv.APIClient()
  118. rdr, err := client.ImagePull(ctx, remote, types.ImagePullOptions{})
  119. assert.NilError(t, err)
  120. defer rdr.Close()
  121. io.Copy(io.Discard, rdr)
  122. // Now, pull a totally different repo with a the same digest
  123. rdr, err = client.ImagePull(ctx, path.Join(registry.DefaultURL, "other:image@"+desc.Digest.String()), types.ImagePullOptions{})
  124. if rdr != nil {
  125. rdr.Close()
  126. }
  127. assert.Assert(t, err != nil, "Expected error, got none: %v", err)
  128. assert.Assert(t, errdefs.IsNotFound(err), err)
  129. }
  130. // TestImagePullNonExisting pulls non-existing images from the central registry, with different
  131. // combinations of implicit tag and library prefix.
  132. func TestImagePullNonExisting(t *testing.T) {
  133. ctx := setupTest(t)
  134. for _, ref := range []string{
  135. "asdfasdf:foobar",
  136. "library/asdfasdf:foobar",
  137. "asdfasdf",
  138. "asdfasdf:latest",
  139. "library/asdfasdf",
  140. "library/asdfasdf:latest",
  141. } {
  142. ref := ref
  143. all := strings.Contains(ref, ":")
  144. t.Run(ref, func(t *testing.T) {
  145. t.Parallel()
  146. client := testEnv.APIClient()
  147. rdr, err := client.ImagePull(ctx, ref, types.ImagePullOptions{
  148. All: all,
  149. })
  150. if err == nil {
  151. rdr.Close()
  152. }
  153. expectedMsg := fmt.Sprintf("pull access denied for %s, repository does not exist or may require 'docker login'", "asdfasdf")
  154. assert.Assert(t, is.ErrorContains(err, expectedMsg))
  155. assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
  156. if all {
  157. // pull -a on a nonexistent registry should fall back as well
  158. assert.Check(t, !strings.Contains(err.Error(), "unauthorized"), `message should not contain "unauthorized"`)
  159. }
  160. })
  161. }
  162. }