github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/integration-cli/docker_cli_save_load_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "testing" 12 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/integration-cli/cli" 15 "github.com/docker/docker/integration-cli/cli/build" 16 "github.com/docker/docker/internal/testutils/specialimage" 17 "gotest.tools/v3/assert" 18 is "gotest.tools/v3/assert/cmp" 19 "gotest.tools/v3/icmd" 20 "gotest.tools/v3/skip" 21 ) 22 23 type DockerCLISaveLoadSuite struct { 24 ds *DockerSuite 25 } 26 27 func (s *DockerCLISaveLoadSuite) TearDownTest(ctx context.Context, c *testing.T) { 28 s.ds.TearDownTest(ctx, c) 29 } 30 31 func (s *DockerCLISaveLoadSuite) OnTimeout(c *testing.T) { 32 s.ds.OnTimeout(c) 33 } 34 35 // save a repo using gz compression and try to load it using stdout 36 func (s *DockerCLISaveLoadSuite) TestSaveXzAndLoadRepoStdout(c *testing.T) { 37 testRequires(c, DaemonIsLinux) 38 name := "test-save-xz-and-load-repo-stdout" 39 cli.DockerCmd(c, "run", "--name", name, "busybox", "true") 40 41 imgRepoName := "foobar-save-load-test-xz-gz" 42 out := cli.DockerCmd(c, "commit", name, imgRepoName).Combined() 43 44 cli.DockerCmd(c, "inspect", imgRepoName) 45 46 repoTarball, err := RunCommandPipelineWithOutput( 47 exec.Command(dockerBinary, "save", imgRepoName), 48 exec.Command("xz", "-c"), 49 exec.Command("gzip", "-c")) 50 assert.NilError(c, err, "failed to save repo: %v %v", out, err) 51 deleteImages(imgRepoName) 52 53 icmd.RunCmd(icmd.Cmd{ 54 Command: []string{dockerBinary, "load"}, 55 Stdin: strings.NewReader(repoTarball), 56 }).Assert(c, icmd.Expected{ 57 ExitCode: 1, 58 }) 59 60 after, _, err := dockerCmdWithError("inspect", imgRepoName) 61 assert.ErrorContains(c, err, "", "the repo should not exist: %v", after) 62 } 63 64 // save a repo using xz+gz compression and try to load it using stdout 65 func (s *DockerCLISaveLoadSuite) TestSaveXzGzAndLoadRepoStdout(c *testing.T) { 66 testRequires(c, DaemonIsLinux) 67 name := "test-save-xz-gz-and-load-repo-stdout" 68 cli.DockerCmd(c, "run", "--name", name, "busybox", "true") 69 70 repoName := "foobar-save-load-test-xz-gz" 71 cli.DockerCmd(c, "commit", name, repoName) 72 73 cli.DockerCmd(c, "inspect", repoName) 74 75 out, err := RunCommandPipelineWithOutput( 76 exec.Command(dockerBinary, "save", repoName), 77 exec.Command("xz", "-c"), 78 exec.Command("gzip", "-c")) 79 assert.NilError(c, err, "failed to save repo: %v %v", out, err) 80 81 deleteImages(repoName) 82 83 icmd.RunCmd(icmd.Cmd{ 84 Command: []string{dockerBinary, "load"}, 85 Stdin: strings.NewReader(out), 86 }).Assert(c, icmd.Expected{ 87 ExitCode: 1, 88 }) 89 90 after, _, err := dockerCmdWithError("inspect", repoName) 91 assert.ErrorContains(c, err, "", "the repo should not exist: %v", after) 92 } 93 94 func (s *DockerCLISaveLoadSuite) TestSaveSingleTag(c *testing.T) { 95 testRequires(c, DaemonIsLinux) 96 imgRepoName := "foobar-save-single-tag-test" 97 cli.DockerCmd(c, "tag", "busybox:latest", fmt.Sprintf("%v:latest", imgRepoName)) 98 99 out := cli.DockerCmd(c, "images", "-q", "--no-trunc", imgRepoName).Stdout() 100 cleanedImageID := strings.TrimSpace(out) 101 102 filesFilter := fmt.Sprintf("(^manifest.json$|%v)", cleanedImageID) 103 if testEnv.UsingSnapshotter() { 104 filesFilter = fmt.Sprintf("(^index.json$|^manifest.json$|%v)", cleanedImageID) 105 } 106 out, err := RunCommandPipelineWithOutput( 107 exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", imgRepoName)), 108 exec.Command("tar", "t"), 109 exec.Command("grep", "-E", filesFilter)) 110 assert.NilError(c, err, "failed to save repo with image ID and index files: %s, %v", out, err) 111 } 112 113 func (s *DockerCLISaveLoadSuite) TestSaveImageId(c *testing.T) { 114 testRequires(c, DaemonIsLinux) 115 116 emptyFSImage := loadSpecialImage(c, specialimage.EmptyFS) 117 118 imgRepoName := "foobar-save-image-id-test" 119 cli.DockerCmd(c, "tag", emptyFSImage, fmt.Sprintf("%v:latest", imgRepoName)) 120 121 out := cli.DockerCmd(c, "images", "-q", "--no-trunc", imgRepoName).Stdout() 122 cleanedLongImageID := strings.TrimPrefix(strings.TrimSpace(out), "sha256:") 123 124 out = cli.DockerCmd(c, "images", "-q", imgRepoName).Stdout() 125 cleanedShortImageID := strings.TrimSpace(out) 126 127 // Make sure IDs are not empty 128 assert.Assert(c, cleanedLongImageID != "", "Id should not be empty.") 129 assert.Assert(c, cleanedShortImageID != "", "Id should not be empty.") 130 131 saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID) 132 tarCmd := exec.Command("tar", "t") 133 134 var err error 135 tarCmd.Stdin, err = saveCmd.StdoutPipe() 136 assert.Assert(c, err == nil, "cannot set stdout pipe for tar: %v", err) 137 grepCmd := exec.Command("grep", cleanedLongImageID) 138 grepCmd.Stdin, err = tarCmd.StdoutPipe() 139 assert.Assert(c, err == nil, "cannot set stdout pipe for grep: %v", err) 140 141 assert.Assert(c, tarCmd.Start() == nil, "tar failed with error: %v", err) 142 assert.Assert(c, saveCmd.Start() == nil, "docker save failed with error: %v", err) 143 defer func() { 144 saveCmd.Wait() 145 tarCmd.Wait() 146 cli.DockerCmd(c, "rmi", imgRepoName) 147 }() 148 149 out, _, err = runCommandWithOutput(grepCmd) 150 151 assert.Assert(c, err == nil, "failed to save repo with image ID: %s, %v", out, err) 152 } 153 154 // save a repo and try to load it using flags 155 func (s *DockerCLISaveLoadSuite) TestSaveAndLoadRepoFlags(c *testing.T) { 156 testRequires(c, DaemonIsLinux) 157 const name = "test-save-and-load-repo-flags" 158 cli.DockerCmd(c, "run", "--name", name, "busybox", "true") 159 160 const imgRepoName = "foobar-save-load-test" 161 162 deleteImages(imgRepoName) 163 cli.DockerCmd(c, "commit", name, imgRepoName) 164 165 beforeStr := cli.DockerCmd(c, "inspect", imgRepoName).Stdout() 166 167 out, err := RunCommandPipelineWithOutput( 168 exec.Command(dockerBinary, "save", imgRepoName), 169 exec.Command(dockerBinary, "load")) 170 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 171 172 afterStr := cli.DockerCmd(c, "inspect", imgRepoName).Stdout() 173 174 var before, after []types.ImageInspect 175 err = json.Unmarshal([]byte(beforeStr), &before) 176 assert.NilError(c, err, "failed to parse inspect 'before' output") 177 err = json.Unmarshal([]byte(afterStr), &after) 178 assert.NilError(c, err, "failed to parse inspect 'after' output") 179 180 assert.Assert(c, is.Len(before, 1)) 181 assert.Assert(c, is.Len(after, 1)) 182 183 if testEnv.UsingSnapshotter() { 184 // Ignore LastTagTime difference with c8d. 185 // It is not stored in the image archive, but in the imageStore 186 // which is a graphdrivers implementation detail. 187 // 188 // It works because we load the image into the same daemon which saved 189 // the image. It would still fail with the graphdrivers if the image 190 // was loaded into a different daemon (which should be the case in a 191 // real-world scenario). 192 before[0].Metadata.LastTagTime = after[0].Metadata.LastTagTime 193 } 194 195 assert.Check(c, is.DeepEqual(before, after), "inspect is not the same after a save / load") 196 } 197 198 func (s *DockerCLISaveLoadSuite) TestSaveWithNoExistImage(c *testing.T) { 199 testRequires(c, DaemonIsLinux) 200 201 imgName := "foobar-non-existing-image" 202 203 out, _, err := dockerCmdWithError("save", "-o", "test-img.tar", imgName) 204 assert.ErrorContains(c, err, "", "save image should fail for non-existing image") 205 assert.Assert(c, strings.Contains(out, fmt.Sprintf("No such image: %s", imgName))) 206 } 207 208 func (s *DockerCLISaveLoadSuite) TestSaveMultipleNames(c *testing.T) { 209 testRequires(c, DaemonIsLinux) 210 211 emptyFSImage := loadSpecialImage(c, specialimage.EmptyFS) 212 213 const imgRepoName = "foobar-save-multi-name-test" 214 215 oneTag := fmt.Sprintf("%v-one:latest", imgRepoName) 216 twoTag := fmt.Sprintf("%v-two:latest", imgRepoName) 217 218 cli.DockerCmd(c, "tag", emptyFSImage, oneTag) 219 cli.DockerCmd(c, "tag", emptyFSImage, twoTag) 220 221 out, err := RunCommandPipelineWithOutput( 222 exec.Command(dockerBinary, "save", strings.TrimSuffix(oneTag, ":latest"), twoTag), 223 exec.Command("tar", "xO", "index.json"), 224 ) 225 assert.NilError(c, err, "failed to save multiple repos: %s, %v", out, err) 226 227 assert.Check(c, is.Contains(out, oneTag)) 228 assert.Check(c, is.Contains(out, twoTag)) 229 } 230 231 // Test loading a weird image where one of the layers is of zero size. 232 // The layer.tar file is actually zero bytes, no padding or anything else. 233 // See issue: 18170 234 func (s *DockerCLISaveLoadSuite) TestLoadZeroSizeLayer(c *testing.T) { 235 // TODO(vvoland): Create an OCI image with 0 bytes layer. 236 skip.If(c, testEnv.UsingSnapshotter(), "input archive is not OCI compatible") 237 238 // this will definitely not work if using remote daemon 239 // very weird test 240 testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon) 241 242 cli.DockerCmd(c, "load", "-i", "testdata/emptyLayer.tar") 243 } 244 245 func (s *DockerCLISaveLoadSuite) TestSaveLoadParents(c *testing.T) { 246 testRequires(c, DaemonIsLinux) 247 skip.If(c, testEnv.UsingSnapshotter(), "Parent image property is not supported with containerd") 248 249 makeImage := func(from string, addfile string) string { 250 id := cli.DockerCmd(c, "run", "-d", from, "touch", addfile).Stdout() 251 id = strings.TrimSpace(id) 252 253 imageID := cli.DockerCmd(c, "commit", id).Stdout() 254 imageID = strings.TrimSpace(imageID) 255 256 cli.DockerCmd(c, "rm", "-f", id) 257 return imageID 258 } 259 260 idFoo := makeImage("busybox", "foo") 261 idBar := makeImage(idFoo, "bar") 262 263 tmpDir, err := os.MkdirTemp("", "save-load-parents") 264 assert.NilError(c, err) 265 defer os.RemoveAll(tmpDir) 266 267 c.Log("tmpdir", tmpDir) 268 269 outfile := filepath.Join(tmpDir, "out.tar") 270 271 cli.DockerCmd(c, "save", "-o", outfile, idBar, idFoo) 272 cli.DockerCmd(c, "rmi", idBar) 273 cli.DockerCmd(c, "load", "-i", outfile) 274 275 inspectOut := inspectField(c, idBar, "Parent") 276 assert.Equal(c, inspectOut, idFoo) 277 278 inspectOut = inspectField(c, idFoo, "Parent") 279 assert.Equal(c, inspectOut, "") 280 } 281 282 func (s *DockerCLISaveLoadSuite) TestSaveLoadNoTag(c *testing.T) { 283 testRequires(c, DaemonIsLinux) 284 285 name := "saveloadnotag" 286 287 buildImageSuccessfully(c, name, build.WithDockerfile("FROM busybox\nENV foo=bar")) 288 id := inspectField(c, name, "Id") 289 290 // Test to make sure that save w/o name just shows imageID during load 291 out, err := RunCommandPipelineWithOutput( 292 exec.Command(dockerBinary, "save", id), 293 exec.Command(dockerBinary, "load")) 294 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 295 296 // Should not show 'name' but should show the image ID during the load 297 assert.Assert(c, !strings.Contains(out, "Loaded image: ")) 298 assert.Assert(c, strings.Contains(out, "Loaded image ID:")) 299 assert.Assert(c, strings.Contains(out, id)) 300 // Test to make sure that save by name shows that name during load 301 out, err = RunCommandPipelineWithOutput( 302 exec.Command(dockerBinary, "save", name), 303 exec.Command(dockerBinary, "load")) 304 assert.NilError(c, err, "failed to save and load repo: %s, %v", out, err) 305 306 assert.Assert(c, strings.Contains(out, "Loaded image: "+name+":latest")) 307 assert.Assert(c, !strings.Contains(out, "Loaded image ID:")) 308 }