save_test.go 6.1 KB

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