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