image_list_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package containerd
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "testing"
  9. "github.com/containerd/containerd"
  10. "github.com/containerd/containerd/content"
  11. "github.com/containerd/containerd/images"
  12. "github.com/containerd/containerd/metadata"
  13. "github.com/containerd/containerd/namespaces"
  14. "github.com/containerd/containerd/snapshots"
  15. "github.com/containerd/log/logtest"
  16. imagetypes "github.com/docker/docker/api/types/image"
  17. daemonevents "github.com/docker/docker/daemon/events"
  18. "github.com/docker/docker/internal/testutils/specialimage"
  19. "github.com/opencontainers/go-digest"
  20. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  21. "gotest.tools/v3/assert"
  22. is "gotest.tools/v3/assert/cmp"
  23. )
  24. func imagesFromIndex(index ...*ocispec.Index) []images.Image {
  25. var imgs []images.Image
  26. for _, idx := range index {
  27. for _, desc := range idx.Manifests {
  28. imgs = append(imgs, images.Image{
  29. Name: desc.Annotations["io.containerd.image.name"],
  30. Target: desc,
  31. })
  32. }
  33. }
  34. return imgs
  35. }
  36. func TestImageList(t *testing.T) {
  37. ctx := namespaces.WithNamespace(context.TODO(), "testing")
  38. blobsDir := t.TempDir()
  39. multilayer, err := specialimage.MultiLayer(blobsDir)
  40. assert.NilError(t, err)
  41. twoplatform, err := specialimage.TwoPlatform(blobsDir)
  42. assert.NilError(t, err)
  43. cs := &blobsDirContentStore{blobs: filepath.Join(blobsDir, "blobs/sha256")}
  44. snapshotter := &testSnapshotterService{}
  45. for _, tc := range []struct {
  46. name string
  47. images []images.Image
  48. opts imagetypes.ListOptions
  49. check func(*testing.T, []*imagetypes.Summary) // Change the type of the check function
  50. }{
  51. {
  52. name: "one multi-layer image",
  53. images: imagesFromIndex(multilayer),
  54. check: func(t *testing.T, all []*imagetypes.Summary) { // Change the type of the check function
  55. assert.Check(t, is.Len(all, 1))
  56. assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String()))
  57. assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"multilayer:latest"}))
  58. },
  59. },
  60. {
  61. name: "one image with two platforms is still one entry",
  62. images: imagesFromIndex(twoplatform),
  63. check: func(t *testing.T, all []*imagetypes.Summary) { // Change the type of the check function
  64. assert.Check(t, is.Len(all, 1))
  65. assert.Check(t, is.Equal(all[0].ID, twoplatform.Manifests[0].Digest.String()))
  66. assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"twoplatform:latest"}))
  67. },
  68. },
  69. {
  70. name: "two images are two entries",
  71. images: imagesFromIndex(multilayer, twoplatform),
  72. check: func(t *testing.T, all []*imagetypes.Summary) { // Change the type of the check function
  73. assert.Check(t, is.Len(all, 2))
  74. assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String()))
  75. assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"multilayer:latest"}))
  76. assert.Check(t, is.Equal(all[1].ID, twoplatform.Manifests[0].Digest.String()))
  77. assert.Check(t, is.DeepEqual(all[1].RepoTags, []string{"twoplatform:latest"}))
  78. },
  79. },
  80. } {
  81. tc := tc
  82. t.Run(tc.name, func(t *testing.T) {
  83. ctx := logtest.WithT(ctx, t)
  84. mdb := newTestDB(ctx, t)
  85. snapshotters := map[string]snapshots.Snapshotter{
  86. containerd.DefaultSnapshotter: snapshotter,
  87. }
  88. service := &ImageService{
  89. images: metadata.NewImageStore(mdb),
  90. containers: emptyTestContainerStore(),
  91. content: cs,
  92. eventsService: daemonevents.New(),
  93. snapshotterServices: snapshotters,
  94. snapshotter: containerd.DefaultSnapshotter,
  95. }
  96. // containerd.Image gets the services directly from containerd.Client
  97. // so we need to create a "fake" containerd.Client with the test services.
  98. c8dCli, err := containerd.New("", containerd.WithServices(
  99. containerd.WithImageStore(service.images),
  100. containerd.WithContentStore(cs),
  101. containerd.WithSnapshotters(snapshotters),
  102. ))
  103. assert.NilError(t, err)
  104. service.client = c8dCli
  105. for _, img := range tc.images {
  106. _, err := service.images.Create(ctx, img)
  107. assert.NilError(t, err)
  108. }
  109. all, err := service.Images(ctx, tc.opts)
  110. assert.NilError(t, err)
  111. sort.Slice(all, func(i, j int) bool {
  112. firstTag := func(idx int) string {
  113. if len(all[idx].RepoTags) > 0 {
  114. return all[idx].RepoTags[0]
  115. }
  116. return ""
  117. }
  118. return firstTag(i) < firstTag(j)
  119. })
  120. tc.check(t, all)
  121. })
  122. }
  123. }
  124. type blobsDirContentStore struct {
  125. blobs string
  126. }
  127. type fileReaderAt struct {
  128. *os.File
  129. }
  130. func (f *fileReaderAt) Size() int64 {
  131. fi, err := f.Stat()
  132. if err != nil {
  133. return -1
  134. }
  135. return fi.Size()
  136. }
  137. func (s *blobsDirContentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
  138. p := filepath.Join(s.blobs, desc.Digest.Encoded())
  139. r, err := os.Open(p)
  140. if err != nil {
  141. return nil, err
  142. }
  143. return &fileReaderAt{r}, nil
  144. }
  145. func (s *blobsDirContentStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
  146. return nil, fmt.Errorf("read-only")
  147. }
  148. func (s *blobsDirContentStore) Status(ctx context.Context, _ string) (content.Status, error) {
  149. return content.Status{}, fmt.Errorf("not implemented")
  150. }
  151. func (s *blobsDirContentStore) Delete(ctx context.Context, dgst digest.Digest) error {
  152. return fmt.Errorf("read-only")
  153. }
  154. func (s *blobsDirContentStore) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
  155. return nil, nil
  156. }
  157. func (s *blobsDirContentStore) Abort(ctx context.Context, ref string) error {
  158. return fmt.Errorf("not implemented")
  159. }
  160. func (s *blobsDirContentStore) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
  161. entries, err := os.ReadDir(s.blobs)
  162. if err != nil {
  163. return err
  164. }
  165. for _, e := range entries {
  166. if e.IsDir() {
  167. continue
  168. }
  169. d := digest.FromString(e.Name())
  170. if d == "" {
  171. continue
  172. }
  173. stat, err := e.Info()
  174. if err != nil {
  175. return err
  176. }
  177. if err := fn(content.Info{Digest: d, Size: stat.Size()}); err != nil {
  178. return err
  179. }
  180. }
  181. return nil
  182. }
  183. func (s *blobsDirContentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
  184. f, err := os.Open(filepath.Join(s.blobs, dgst.Encoded()))
  185. if err != nil {
  186. return content.Info{}, err
  187. }
  188. defer f.Close()
  189. stat, err := f.Stat()
  190. if err != nil {
  191. return content.Info{}, err
  192. }
  193. return content.Info{
  194. Digest: dgst,
  195. Size: stat.Size(),
  196. }, nil
  197. }
  198. func (s *blobsDirContentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
  199. return content.Info{}, fmt.Errorf("read-only")
  200. }