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 <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi 2016-03-21 13:52:36 -07:00
parent c22d09f563
commit faeff5118f
4 changed files with 110 additions and 0 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -18,6 +18,7 @@ type manifestItem struct {
Config string
RepoTags []string
Layers []string
Parent image.ID `json:",omitempty"`
}
type tarexporter struct {

View File

@ -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, "")
}