save_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package image
  2. import (
  3. "archive/tar"
  4. "encoding/json"
  5. "io"
  6. "io/fs"
  7. "os"
  8. "path/filepath"
  9. "reflect"
  10. "sort"
  11. "strings"
  12. "testing"
  13. "time"
  14. "github.com/cpuguy83/tar2go"
  15. containertypes "github.com/docker/docker/api/types/container"
  16. "github.com/docker/docker/integration/internal/build"
  17. "github.com/docker/docker/integration/internal/container"
  18. "github.com/docker/docker/pkg/archive"
  19. "github.com/docker/docker/testutil/fakecontext"
  20. "github.com/opencontainers/go-digest"
  21. "gotest.tools/v3/assert"
  22. "gotest.tools/v3/assert/cmp"
  23. is "gotest.tools/v3/assert/cmp"
  24. "gotest.tools/v3/skip"
  25. )
  26. type imageSaveManifestEntry struct {
  27. Config string
  28. RepoTags []string
  29. Layers []string
  30. }
  31. func tarIndexFS(t *testing.T, rdr io.Reader) fs.FS {
  32. t.Helper()
  33. dir := t.TempDir()
  34. f, err := os.Create(filepath.Join(dir, "image.tar"))
  35. assert.NilError(t, err)
  36. // Do not close at the end of this function otherwise the indexer won't work
  37. t.Cleanup(func() { f.Close() })
  38. _, err = io.Copy(f, rdr)
  39. assert.NilError(t, err)
  40. return tar2go.NewIndex(f).FS()
  41. }
  42. func TestSaveCheckTimes(t *testing.T) {
  43. t.Parallel()
  44. ctx := setupTest(t)
  45. client := testEnv.APIClient()
  46. const repoName = "busybox:latest"
  47. img, _, err := client.ImageInspectWithRaw(ctx, repoName)
  48. assert.NilError(t, err)
  49. rdr, err := client.ImageSave(ctx, []string{repoName})
  50. assert.NilError(t, err)
  51. tarfs := tarIndexFS(t, rdr)
  52. dt, err := fs.ReadFile(tarfs, "manifest.json")
  53. assert.NilError(t, err)
  54. var ls []imageSaveManifestEntry
  55. assert.NilError(t, json.Unmarshal(dt, &ls))
  56. assert.Assert(t, cmp.Len(ls, 1))
  57. info, err := fs.Stat(tarfs, ls[0].Config)
  58. assert.NilError(t, err)
  59. created, err := time.Parse(time.RFC3339, img.Created)
  60. assert.NilError(t, err)
  61. if testEnv.UsingSnapshotter() {
  62. // containerd archive export sets the mod time to zero.
  63. assert.Check(t, is.Equal(info.ModTime(), time.Unix(0, 0)))
  64. } else {
  65. assert.Check(t, is.Equal(info.ModTime().Format(time.RFC3339), created.Format(time.RFC3339)))
  66. }
  67. }
  68. func TestSaveRepoWithMultipleImages(t *testing.T) {
  69. ctx := setupTest(t)
  70. client := testEnv.APIClient()
  71. makeImage := func(from string, tag string) string {
  72. id := container.Create(ctx, t, client, func(cfg *container.TestContainerConfig) {
  73. cfg.Config.Image = from
  74. cfg.Config.Cmd = []string{"true"}
  75. })
  76. res, err := client.ContainerCommit(ctx, id, containertypes.CommitOptions{Reference: tag})
  77. assert.NilError(t, err)
  78. err = client.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
  79. assert.NilError(t, err)
  80. return res.ID
  81. }
  82. busyboxImg, _, err := client.ImageInspectWithRaw(ctx, "busybox:latest")
  83. assert.NilError(t, err)
  84. const repoName = "foobar-save-multi-images-test"
  85. const tagFoo = repoName + ":foo"
  86. const tagBar = repoName + ":bar"
  87. idFoo := makeImage("busybox:latest", tagFoo)
  88. idBar := makeImage("busybox:latest", tagBar)
  89. idBusybox := busyboxImg.ID
  90. rdr, err := client.ImageSave(ctx, []string{repoName, "busybox:latest"})
  91. assert.NilError(t, err)
  92. defer rdr.Close()
  93. tarfs := tarIndexFS(t, rdr)
  94. dt, err := fs.ReadFile(tarfs, "manifest.json")
  95. assert.NilError(t, err)
  96. var mfstLs []imageSaveManifestEntry
  97. assert.NilError(t, json.Unmarshal(dt, &mfstLs))
  98. actual := make([]string, 0, len(mfstLs))
  99. for _, m := range mfstLs {
  100. actual = append(actual, strings.TrimPrefix(m.Config, "blobs/sha256/"))
  101. // make sure the blob actually exists
  102. _, err = fs.Stat(tarfs, m.Config)
  103. assert.Check(t, err)
  104. }
  105. expected := []string{idBusybox, idFoo, idBar}
  106. // prefixes are not in tar
  107. for i := range expected {
  108. expected[i] = digest.Digest(expected[i]).Encoded()
  109. }
  110. // With snapshotters, ID of the image is the ID of the manifest/index
  111. // With graphdrivers, ID of the image is the ID of the image config
  112. if testEnv.UsingSnapshotter() {
  113. // ID of image won't match the Config ID from manifest.json
  114. // Just check if manifests exist in blobs
  115. for _, blob := range expected {
  116. _, err = fs.Stat(tarfs, "blobs/sha256/"+blob)
  117. assert.Check(t, err)
  118. }
  119. } else {
  120. sort.Strings(actual)
  121. sort.Strings(expected)
  122. assert.Assert(t, cmp.DeepEqual(actual, expected), "archive does not contains the right layers: got %v, expected %v", actual, expected)
  123. }
  124. }
  125. func TestSaveDirectoryPermissions(t *testing.T) {
  126. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Test is looking at linux specific details")
  127. ctx := setupTest(t)
  128. client := testEnv.APIClient()
  129. layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
  130. layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
  131. dockerfile := `FROM busybox
  132. RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a
  133. RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`
  134. imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile)))
  135. rdr, err := client.ImageSave(ctx, []string{imgID})
  136. assert.NilError(t, err)
  137. defer rdr.Close()
  138. tarfs := tarIndexFS(t, rdr)
  139. dt, err := fs.ReadFile(tarfs, "manifest.json")
  140. assert.NilError(t, err)
  141. var mfstLs []imageSaveManifestEntry
  142. assert.NilError(t, json.Unmarshal(dt, &mfstLs))
  143. var found bool
  144. for _, p := range mfstLs[0].Layers {
  145. var entriesSansDev []string
  146. f, err := tarfs.Open(p)
  147. assert.NilError(t, err)
  148. entries, err := listTar(f)
  149. f.Close()
  150. assert.NilError(t, err)
  151. for _, e := range entries {
  152. if !strings.Contains(e, "dev/") {
  153. entriesSansDev = append(entriesSansDev, e)
  154. }
  155. }
  156. assert.NilError(t, err, "encountered error while listing tar entries: %s", err)
  157. if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) {
  158. found = true
  159. break
  160. }
  161. }
  162. assert.Assert(t, found, "failed to find the layer with the right content listing")
  163. }
  164. func listTar(f io.Reader) ([]string, error) {
  165. // If using the containerd snapshotter, the tar file may be compressed
  166. dec, err := archive.DecompressStream(f)
  167. if err != nil {
  168. return nil, err
  169. }
  170. defer dec.Close()
  171. tr := tar.NewReader(dec)
  172. var entries []string
  173. for {
  174. th, err := tr.Next()
  175. if err == io.EOF {
  176. // end of tar archive
  177. return entries, nil
  178. }
  179. if err != nil {
  180. return entries, err
  181. }
  182. entries = append(entries, th.Name)
  183. }
  184. }