docker save: ability to save multiple images

Now from a single invocation of `docker save`, you can specify multiple
images to include in the output tar, or even just multiple tags of a
particular image/repo.

```
> docker save -o bundle.tar busybox ubuntu:lucid ubuntu:saucy fedora:latest
> tar tf ./bundle.tar | wc -l
42
> tar xOf ./bundle.tar repositories
{"busybox":{"latest":"2d8e5b282c81244037eb15b2068e1c46319c1a42b80493acb128da24b2090739"},"fedora":{"latest":"58394af373423902a1b97f209a31e3777932d9321ef10e64feaaa7b4df609cf9"},"ubuntu":{"lucid":"9cc9ea5ea540116b89e41898dd30858107c1175260fb7ff50322b34704092232","saucy":"9f676bd305a43a931a8d98b13e5840ffbebcd908370765373315926024c7c35e"}}
```

Further, this fixes the bug where the `repositories` file is not created
when saving a specific tag of an image (e.g. ubuntu:latest)

document multi-image save and updated API docs

Docker-DCO-1.1-Signed-off-by: Vincent Batts <vbatts@redhat.com> (github: vbatts)
This commit is contained in:
Vincent Batts
2014-08-28 20:22:33 -04:00
parent 31d2701377
commit e64131d1bf
6 changed files with 147 additions and 51 deletions
+15 -5
View File
@@ -2257,14 +2257,14 @@ func (cli *DockerCli) CmdCp(args ...string) error {
}
func (cli *DockerCli) CmdSave(args ...string) error {
cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to STDOUT by default)")
cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)")
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
if err := cmd.Parse(args); err != nil {
return err
}
if cmd.NArg() != 1 {
if cmd.NArg() < 1 {
cmd.Usage()
return nil
}
@@ -2279,9 +2279,19 @@ func (cli *DockerCli) CmdSave(args ...string) error {
return err
}
}
image := cmd.Arg(0)
if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
return err
if len(cmd.Args()) == 1 {
image := cmd.Arg(0)
if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
return err
}
} else {
v := url.Values{}
for _, arg := range cmd.Args() {
v.Add("names", arg)
}
if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil {
return err
}
}
return nil
}
+10 -1
View File
@@ -611,10 +611,18 @@ func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWr
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
if version.GreaterThan("1.0") {
w.Header().Set("Content-Type", "application/x-tar")
}
job := eng.Job("image_export", vars["name"])
var job *engine.Job
if name, ok := vars["name"]; ok {
job = eng.Job("image_export", name)
} else {
job = eng.Job("image_export", r.Form["names"]...)
}
job.Stdout.Add(w)
return job.Run()
}
@@ -1105,6 +1113,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/get": getImagesGet,
"/images/{name:.*}/get": getImagesGet,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,