pull_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package image
  2. import (
  3. "context"
  4. "encoding/json"
  5. "io"
  6. "os"
  7. "path"
  8. "testing"
  9. "github.com/containerd/containerd"
  10. "github.com/containerd/containerd/content"
  11. "github.com/containerd/containerd/content/local"
  12. "github.com/containerd/containerd/images"
  13. "github.com/containerd/containerd/platforms"
  14. "github.com/docker/docker/api/types"
  15. "github.com/docker/docker/api/types/versions"
  16. "github.com/docker/docker/errdefs"
  17. "github.com/docker/docker/testutil/registry"
  18. "github.com/opencontainers/go-digest"
  19. "github.com/opencontainers/image-spec/specs-go"
  20. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  21. "gotest.tools/v3/assert"
  22. "gotest.tools/v3/skip"
  23. )
  24. func TestImagePullPlatformInvalid(t *testing.T) {
  25. skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "experimental in older versions")
  26. defer setupTest(t)()
  27. client := testEnv.APIClient()
  28. ctx := context.Background()
  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. defer setupTest(t)()
  102. reg := registry.NewV2(t, registry.WithStdout(os.Stdout), registry.WithStderr(os.Stderr))
  103. defer reg.Close()
  104. reg.WaitReady(t)
  105. ctx := context.Background()
  106. // First create an image and upload it to our local registry
  107. // Then we'll download it so that we can make sure the content is available in dockerd's manifest cache.
  108. // Then we'll try to pull the same digest but with a different repo name.
  109. dir := t.TempDir()
  110. store, err := local.NewStore(dir)
  111. assert.NilError(t, err)
  112. desc := createTestImage(ctx, t, store)
  113. remote := path.Join(registry.DefaultURL, "test:latest")
  114. c8dClient, err := containerd.New("", containerd.WithServices(containerd.WithContentStore(store)))
  115. assert.NilError(t, err)
  116. c8dClient.Push(ctx, remote, desc)
  117. assert.NilError(t, err)
  118. client := testEnv.APIClient()
  119. rdr, err := client.ImagePull(ctx, remote, types.ImagePullOptions{})
  120. assert.NilError(t, err)
  121. defer rdr.Close()
  122. io.Copy(io.Discard, rdr)
  123. // Now, pull a totally different repo with a the same digest
  124. rdr, err = client.ImagePull(ctx, path.Join(registry.DefaultURL, "other:image@"+desc.Digest.String()), types.ImagePullOptions{})
  125. if rdr != nil {
  126. rdr.Close()
  127. }
  128. assert.Assert(t, err != nil, "Expected error, got none: %v", err)
  129. assert.Assert(t, errdefs.IsNotFound(err), err)
  130. }