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