github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/integration-cli/docker_cli_save_load_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "reflect" 12 "regexp" 13 "sort" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/docker/docker/integration-cli/cli/build" 19 "github.com/opencontainers/go-digest" 20 "gotest.tools/v3/assert" 21 is "gotest.tools/v3/assert/cmp" 22 "gotest.tools/v3/icmd" 23 ) 24 25 type DockerCLISaveLoadSuite struct { 26 ds *DockerSuite 27 } 28 29 func (s *DockerCLISaveLoadSuite) TearDownTest(c *testing.T) { 30 s.ds.TearDownTest(c) 31 } 32 33 func (s *DockerCLISaveLoadSuite) OnTimeout(c *testing.T) { 34 s.ds.OnTimeout(c) 35 } 36 37 // save a repo using gz compression and try to load it using stdout 38 func (s *DockerCLISaveLoadSuite) TestSaveXzAndLoadRepoStdout(c *testing.T) { 39 testRequires(c, DaemonIsLinux) 40 name := "test-save-xz-and-load-repo-stdout" 41 dockerCmd(c, "run", "--name", name, "busybox", "true") 42 43 repoName := "foobar-save-load-test-xz-gz" 44 out, _ := dockerCmd(c, "commit", name, repoName) 45 46 dockerCmd(c, "inspect", repoName) 47 48 repoTarball, err := RunCommandPipelineWithOutput( 49 exec.Command(dockerBinary, "save", repoName), 50 exec.Command("xz", "-c"), 51 exec.Command("gzip", "-c")) 52 assert.NilError(c, err, "failed to save repo: %v %v", out, err) 53 deleteImages(repoName) 54 55 icmd.RunCmd(icmd.Cmd{ 56 Command: []string{dockerBinary, "load"}, 57 Stdin: strings.NewReader(repoTarball), 58 }).Assert(c, icmd.Expected{ 59 ExitCode: 1, 60 }) 61 62 after, _, err := dockerCmdWithError("inspect", repoName) 63 assert.ErrorContains(c, err, "", "the repo should not exist: %v", after) 64 } 65 66 // save a repo using xz+gz compression and try to load it using stdout 67 func (s *DockerCLISaveLoadSuite) TestSaveXzGzAndLoadRepoStdout(c *testing.T) { 68 testRequires(c, DaemonIsLinux) 69 name := "test-save-xz-gz-and-load-repo-stdout" 70 dockerCmd(c, "run", "--name", name, "busybox", "true") 71 72 repoName := "foobar-save-load-test-xz-gz" 73 dockerCmd(c, "commit", name, repoName) 74 75 dockerCmd(c, "inspect", repoName) 76 77 out, err := RunCommandPipelineWithOutput( 78 exec.Command(dockerBinary, "save", repoName), 79 exec.Command("xz", "-c"), 80 exec.Command("gzip", "-c")) 81 assert.NilError(c, err, "failed to save repo: %v %v", out, err) 82 83 deleteImages(repoName) 84 85 icmd.RunCmd(icmd.Cmd{ 86 Command: []string{dockerBinary, "load"}, 87 Stdin: strings.NewReader(out), 88 }).Assert(c, icmd.Expected{ 89 ExitCode: 1, 90 }) 91 92 after, _, err := dockerCmdWithError("inspect", repoName) 93 assert.ErrorContains(c, err, "", "the repo should not exist: %v", after) 94 } 95 96 func (s *DockerCLISaveLoadSuite) TestSaveSingleTag(c *testing.T) { 97 testRequires(c, DaemonIsLinux) 98 repoName := "foobar-save-single-tag-test" 99 dockerCmd(c, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName)) 100 101 out, _ := dockerCmd(c, "images", "-q", "--no-trunc", repoName) 102 cleanedImageID := strings.TrimSpace(out) 103 104 out, err := RunCommandPipelineWithOutput( 105 exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", repoName)), 106 exec.Command("tar", "t"), 107 exec.Command("grep", "-E", fmt.Sprintf("(^repositories$|%v)", cleanedImageID))) 108 assert.NilError(c, err, "failed to save repo with image ID and 'repositories' file: %s, %v", out, err) 109 } 110 111 func (s *DockerCLISaveLoadSuite) TestSaveCheckTimes(c *testing.T) { 112 testRequires(c, DaemonIsLinux) 113 repoName := "busybox:latest" 114 out, _ := dockerCmd(c, "inspect", repoName) 115 var data []struct { 116 ID string 117 Created time.Time 118 } 119 err := json.Unmarshal([]byte(out), &data) 120 assert.NilError(c, err, "failed to marshal from %q: err %v", repoName, err) 121 assert.Assert(c, len(data) != 0, "failed to marshal the data from %q", repoName) 122 tarTvTimeFormat := "2006-01-02 15:04" 123 out, err = RunCommandPipelineWithOutput( 124 exec.Command(dockerBinary, "save", repoName), 125 exec.Command("tar", "tv"), 126 exec.Command("grep", "-E", fmt.Sprintf("%s %s", data[0].Created.Format(tarTvTimeFormat), digest.Digest(data[0].ID).Encoded()))) 127 assert.NilError(c, err, "failed to save repo with image ID and 'repositories' file: %s, %v", out, err) 128 } 129 130 func (s *DockerCLISaveLoadSuite) TestSaveImageId(c *testing.T) { 131 testRequires(c, DaemonIsLinux) 132 repoName := "foobar-save-image-id-test" 133 dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName)) 134 135 out, _ := dockerCmd(c, "images", "-q", "--no-trunc", repoName) 136 cleanedLongImageID := strings.TrimPrefix(strings.TrimSpace(out), "sha256:") 137 138 out, _ = dockerCmd(c, "images", "-q", repoName) 139 cleanedShortImageID := strings.TrimSpace(out) 140 141 // Make sure IDs are not empty 142 assert.Assert(c, cleanedLongImageID != "", "Id should not be empty.") 143 assert.Assert(c, cleanedShortImageID != "", "Id should not be empty.") 144 145 saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID) 146 tarCmd := exec.Command("tar", "t") 147 148 var err error 149 tarCmd.Stdin, err = saveCmd.StdoutPipe() 150 assert.Assert(c, err == nil, "cannot set stdout pipe for tar: %v", err) 151 grepCmd := exec.Command("grep", cleanedLongImageID) 152 grepCmd.Stdin, err = tarCmd.StdoutPipe() 153 assert.Assert(c, err == nil, "cannot set stdout pipe for grep: %v", err) 154 155 assert.Assert(c, tarCmd.Start() == nil, "tar failed with error: %v", err) 156 assert.Assert(c, saveCmd.Start() == nil, "docker save failed with error: %v", err) 157 defer func() { 158 saveCmd.Wait() 159 tarCmd.Wait() 160 dockerCmd(c, "rmi", repoName) 161 }() 162 163 out, _, err = runCommandWithOutput(grepCmd) 164 165 assert.Assert(c, err == nil, "failed to save repo with image ID: %s, %v", out, err) 166 } 167 168 // save a repo and try to load it using flags 169 func (s *DockerCLISaveLoadSuite) TestSaveAndLoadRepoFlags(c *testing.T) { 170 testRequires(c, DaemonIsLinux) 171 name := "test-save-and-load-repo-flags" 172 dockerCmd(c, "run", "--name", name, "busybox", "true") 173 174 repoName := "foobar-save-load-test" 175 176 deleteImages(repoName) 177 dockerCmd(c, "commit", name, repoName) 178 179 before, _ := dockerCmd(c, "inspect", repoName) 180 181 out, err := RunCommandPipelineWithOutput( 182 exec.Command(dockerBinary, "save", repoName), 183 exec.Command(dockerBinary, "load")) 184 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 185 186 after, _ := dockerCmd(c, "inspect", repoName) 187 assert.Equal(c, before, after, "inspect is not the same after a save / load") 188 } 189 190 func (s *DockerCLISaveLoadSuite) TestSaveWithNoExistImage(c *testing.T) { 191 testRequires(c, DaemonIsLinux) 192 193 imgName := "foobar-non-existing-image" 194 195 out, _, err := dockerCmdWithError("save", "-o", "test-img.tar", imgName) 196 assert.ErrorContains(c, err, "", "save image should fail for non-existing image") 197 assert.Assert(c, strings.Contains(out, fmt.Sprintf("No such image: %s", imgName))) 198 } 199 200 func (s *DockerCLISaveLoadSuite) TestSaveMultipleNames(c *testing.T) { 201 testRequires(c, DaemonIsLinux) 202 repoName := "foobar-save-multi-name-test" 203 204 // Make one image 205 dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v-one:latest", repoName)) 206 207 // Make two images 208 dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName)) 209 210 out, err := RunCommandPipelineWithOutput( 211 exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)), 212 exec.Command("tar", "xO", "repositories"), 213 exec.Command("grep", "-q", "-E", "(-one|-two)"), 214 ) 215 assert.NilError(c, err, "failed to save multiple repos: %s, %v", out, err) 216 } 217 218 func (s *DockerCLISaveLoadSuite) TestSaveRepoWithMultipleImages(c *testing.T) { 219 testRequires(c, DaemonIsLinux) 220 makeImage := func(from string, tag string) string { 221 var ( 222 out string 223 ) 224 out, _ = dockerCmd(c, "run", "-d", from, "true") 225 cleanedContainerID := strings.TrimSpace(out) 226 227 out, _ = dockerCmd(c, "commit", cleanedContainerID, tag) 228 imageID := strings.TrimSpace(out) 229 return imageID 230 } 231 232 repoName := "foobar-save-multi-images-test" 233 tagFoo := repoName + ":foo" 234 tagBar := repoName + ":bar" 235 236 idFoo := makeImage("busybox:latest", tagFoo) 237 idBar := makeImage("busybox:latest", tagBar) 238 239 deleteImages(repoName) 240 241 // create the archive 242 out, err := RunCommandPipelineWithOutput( 243 exec.Command(dockerBinary, "save", repoName, "busybox:latest"), 244 exec.Command("tar", "t")) 245 assert.NilError(c, err, "failed to save multiple images: %s, %v", out, err) 246 247 lines := strings.Split(strings.TrimSpace(out), "\n") 248 var actual []string 249 for _, l := range lines { 250 if regexp.MustCompile(`^[a-f0-9]{64}\.json$`).Match([]byte(l)) { 251 actual = append(actual, strings.TrimSuffix(l, ".json")) 252 } 253 } 254 255 // make the list of expected layers 256 out = inspectField(c, "busybox:latest", "Id") 257 expected := []string{strings.TrimSpace(out), idFoo, idBar} 258 259 // prefixes are not in tar 260 for i := range expected { 261 expected[i] = digest.Digest(expected[i]).Encoded() 262 } 263 264 sort.Strings(actual) 265 sort.Strings(expected) 266 assert.Assert(c, is.DeepEqual(actual, expected), "archive does not contains the right layers: got %v, expected %v, output: %q", actual, expected, out) 267 } 268 269 // Issue #6722 #5892 ensure directories are included in changes 270 func (s *DockerCLISaveLoadSuite) TestSaveDirectoryPermissions(c *testing.T) { 271 testRequires(c, DaemonIsLinux) 272 layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} 273 layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} 274 275 name := "save-directory-permissions" 276 tmpDir, err := os.MkdirTemp("", "save-layers-with-directories") 277 assert.Assert(c, err == nil, "failed to create temporary directory: %s", err) 278 extractionDirectory := filepath.Join(tmpDir, "image-extraction-dir") 279 os.Mkdir(extractionDirectory, 0777) 280 281 defer os.RemoveAll(tmpDir) 282 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 283 RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a 284 RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`)) 285 286 out, err := RunCommandPipelineWithOutput( 287 exec.Command(dockerBinary, "save", name), 288 exec.Command("tar", "-xf", "-", "-C", extractionDirectory), 289 ) 290 assert.NilError(c, err, "failed to save and extract image: %s", out) 291 292 dirs, err := os.ReadDir(extractionDirectory) 293 assert.NilError(c, err, "failed to get a listing of the layer directories: %s", err) 294 295 found := false 296 for _, entry := range dirs { 297 var entriesSansDev []string 298 if entry.IsDir() { 299 layerPath := filepath.Join(extractionDirectory, entry.Name(), "layer.tar") 300 301 f, err := os.Open(layerPath) 302 assert.NilError(c, err, "failed to open %s: %s", layerPath, err) 303 304 defer f.Close() 305 306 entries, err := listTar(f) 307 for _, e := range entries { 308 if !strings.Contains(e, "dev/") { 309 entriesSansDev = append(entriesSansDev, e) 310 } 311 } 312 assert.NilError(c, err, "encountered error while listing tar entries: %s", err) 313 314 if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) { 315 found = true 316 break 317 } 318 } 319 } 320 321 assert.Assert(c, found, "failed to find the layer with the right content listing") 322 } 323 324 func listTar(f io.Reader) ([]string, error) { 325 tr := tar.NewReader(f) 326 var entries []string 327 328 for { 329 th, err := tr.Next() 330 if err == io.EOF { 331 // end of tar archive 332 return entries, nil 333 } 334 if err != nil { 335 return entries, err 336 } 337 entries = append(entries, th.Name) 338 } 339 } 340 341 // Test loading a weird image where one of the layers is of zero size. 342 // The layer.tar file is actually zero bytes, no padding or anything else. 343 // See issue: 18170 344 func (s *DockerCLISaveLoadSuite) TestLoadZeroSizeLayer(c *testing.T) { 345 // this will definitely not work if using remote daemon 346 // very weird test 347 testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon) 348 349 dockerCmd(c, "load", "-i", "testdata/emptyLayer.tar") 350 } 351 352 func (s *DockerCLISaveLoadSuite) TestSaveLoadParents(c *testing.T) { 353 testRequires(c, DaemonIsLinux) 354 355 makeImage := func(from string, addfile string) string { 356 var ( 357 out string 358 ) 359 out, _ = dockerCmd(c, "run", "-d", from, "touch", addfile) 360 cleanedContainerID := strings.TrimSpace(out) 361 362 out, _ = dockerCmd(c, "commit", cleanedContainerID) 363 imageID := strings.TrimSpace(out) 364 365 dockerCmd(c, "rm", "-f", cleanedContainerID) 366 return imageID 367 } 368 369 idFoo := makeImage("busybox", "foo") 370 idBar := makeImage(idFoo, "bar") 371 372 tmpDir, err := os.MkdirTemp("", "save-load-parents") 373 assert.NilError(c, err) 374 defer os.RemoveAll(tmpDir) 375 376 c.Log("tmpdir", tmpDir) 377 378 outfile := filepath.Join(tmpDir, "out.tar") 379 380 dockerCmd(c, "save", "-o", outfile, idBar, idFoo) 381 dockerCmd(c, "rmi", idBar) 382 dockerCmd(c, "load", "-i", outfile) 383 384 inspectOut := inspectField(c, idBar, "Parent") 385 assert.Equal(c, inspectOut, idFoo) 386 387 inspectOut = inspectField(c, idFoo, "Parent") 388 assert.Equal(c, inspectOut, "") 389 } 390 391 func (s *DockerCLISaveLoadSuite) TestSaveLoadNoTag(c *testing.T) { 392 testRequires(c, DaemonIsLinux) 393 394 name := "saveloadnotag" 395 396 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENV foo=bar")) 397 id := inspectField(c, name, "Id") 398 399 // Test to make sure that save w/o name just shows imageID during load 400 out, err := RunCommandPipelineWithOutput( 401 exec.Command(dockerBinary, "save", id), 402 exec.Command(dockerBinary, "load")) 403 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 404 405 // Should not show 'name' but should show the image ID during the load 406 assert.Assert(c, !strings.Contains(out, "Loaded image: ")) 407 assert.Assert(c, strings.Contains(out, "Loaded image ID:")) 408 assert.Assert(c, strings.Contains(out, id)) 409 // Test to make sure that save by name shows that name during load 410 out, err = RunCommandPipelineWithOutput( 411 exec.Command(dockerBinary, "save", name), 412 exec.Command(dockerBinary, "load")) 413 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 414 415 assert.Assert(c, strings.Contains(out, "Loaded image: "+name+":latest")) 416 assert.Assert(c, !strings.Contains(out, "Loaded image ID:")) 417 }