From faeff5118f710f7c4f6173c309b52aaba24118c9 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 21 Mar 2016 13:52:36 -0700 Subject: [PATCH] Add parent references support to load/save Restores the correct parent chain relationship between images on docker load if multiple images have been saved. Signed-off-by: Tonis Tiigi --- image/tarexport/load.go | 60 ++++++++++++++++++++ image/tarexport/save.go | 10 ++++ image/tarexport/tarexport.go | 1 + integration-cli/docker_cli_save_load_test.go | 39 +++++++++++++ 4 files changed, 110 insertions(+) diff --git a/image/tarexport/load.go b/image/tarexport/load.go index 0216105be2..6a90385faa 100644 --- a/image/tarexport/load.go +++ b/image/tarexport/load.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "github.com/Sirupsen/logrus" "github.com/docker/docker/image" @@ -58,6 +59,8 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) return err } + var parentLinks []parentLink + for _, m := range manifest { configPath, err := safePath(tmpDir, m.Config) if err != nil { @@ -117,11 +120,35 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) l.setLoadedTag(ref, imgID, outStream) } + parentLinks = append(parentLinks, parentLink{imgID, m.Parent}) + } + + for _, p := range validatedParentLinks(parentLinks) { + if p.parentID != "" { + if err := l.setParentID(p.id, p.parentID); err != nil { + return err + } + } } return nil } +func (l *tarexporter) setParentID(id, parentID image.ID) error { + img, err := l.is.Get(id) + if err != nil { + return err + } + parent, err := l.is.Get(parentID) + if err != nil { + return err + } + if !checkValidParent(img, parent) { + return fmt.Errorf("image %v is not a valid parent for %v", parent.ID, img.ID) + } + return l.is.SetParent(id, parentID) +} + func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, progressOutput progress.Output) (layer.Layer, error) { rawTar, err := os.Open(filename) if err != nil { @@ -309,3 +336,36 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str func safePath(base, path string) (string, error) { return symlink.FollowSymlinkInScope(filepath.Join(base, path), base) } + +type parentLink struct { + id, parentID image.ID +} + +func validatedParentLinks(pl []parentLink) (ret []parentLink) { +mainloop: + for i, p := range pl { + ret = append(ret, p) + for _, p2 := range pl { + if p2.id == p.parentID && p2.id != p.id { + continue mainloop + } + } + ret[i].parentID = "" + } + return +} + +func checkValidParent(img, parent *image.Image) bool { + if len(img.History) == 0 && len(parent.History) == 0 { + return true // having history is not mandatory + } + if len(img.History)-len(parent.History) != 1 { + return false + } + for i, h := range parent.History { + if !reflect.DeepEqual(h, img.History[i]) { + return false + } + } + return true +} diff --git a/image/tarexport/save.go b/image/tarexport/save.go index c92227fa49..9ec3cc9f52 100644 --- a/image/tarexport/save.go +++ b/image/tarexport/save.go @@ -128,6 +128,7 @@ func (s *saveSession) save(outStream io.Writer) error { reposLegacy := make(map[string]map[string]string) var manifest []manifestItem + var parentLinks []parentLink for id, imageDescr := range s.images { if err = s.saveImage(id); err != nil { @@ -154,6 +155,15 @@ func (s *saveSession) save(outStream io.Writer) error { RepoTags: repoTags, Layers: layers, }) + + parentID, _ := s.is.GetParent(id) + parentLinks = append(parentLinks, parentLink{id, parentID}) + } + + for i, p := range validatedParentLinks(parentLinks) { + if p.parentID != "" { + manifest[i].Parent = p.parentID + } } if len(reposLegacy) > 0 { diff --git a/image/tarexport/tarexport.go b/image/tarexport/tarexport.go index cc8cdc8533..5e20877779 100644 --- a/image/tarexport/tarexport.go +++ b/image/tarexport/tarexport.go @@ -18,6 +18,7 @@ type manifestItem struct { Config string RepoTags []string Layers []string + Parent image.ID `json:",omitempty"` } type tarexporter struct { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index 4479588ef3..b21c987bb6 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -311,3 +311,42 @@ func (s *DockerSuite) TestLoadZeroSizeLayer(c *check.C) { dockerCmd(c, "load", "-i", "fixtures/load/emptyLayer.tar") } + +func (s *DockerSuite) TestSaveLoadParents(c *check.C) { + testRequires(c, DaemonIsLinux) + + makeImage := func(from string, addfile string) string { + var ( + out string + ) + out, _ = dockerCmd(c, "run", "-d", from, "touch", addfile) + cleanedContainerID := strings.TrimSpace(out) + + out, _ = dockerCmd(c, "commit", cleanedContainerID) + imageID := strings.TrimSpace(out) + + dockerCmd(c, "rm", cleanedContainerID) + return imageID + } + + idFoo := makeImage("busybox", "foo") + idBar := makeImage(idFoo, "bar") + + tmpDir, err := ioutil.TempDir("", "save-load-parents") + c.Assert(err, checker.IsNil) + defer os.RemoveAll(tmpDir) + + c.Log("tmpdir", tmpDir) + + outfile := filepath.Join(tmpDir, "out.tar") + + dockerCmd(c, "save", "-o", outfile, idBar, idFoo) + dockerCmd(c, "rmi", idBar) + dockerCmd(c, "load", "-i", outfile) + + inspectOut := inspectField(c, idBar, "Parent") + c.Assert(inspectOut, checker.Equals, idFoo) + + inspectOut = inspectField(c, idFoo, "Parent") + c.Assert(inspectOut, checker.Equals, "") +}