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