Browse code

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>

Tonis Tiigi authored on 2016/03/22 05:52:36
Showing 4 changed files
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"io/ioutil"
8 8
 	"os"
9 9
 	"path/filepath"
10
+	"reflect"
10 11
 
11 12
 	"github.com/Sirupsen/logrus"
12 13
 	"github.com/docker/docker/image"
... ...
@@ -58,6 +59,8 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
58 58
 		return err
59 59
 	}
60 60
 
61
+	var parentLinks []parentLink
62
+
61 63
 	for _, m := range manifest {
62 64
 		configPath, err := safePath(tmpDir, m.Config)
63 65
 		if err != nil {
... ...
@@ -117,11 +120,35 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
117 117
 			l.setLoadedTag(ref, imgID, outStream)
118 118
 		}
119 119
 
120
+		parentLinks = append(parentLinks, parentLink{imgID, m.Parent})
121
+	}
122
+
123
+	for _, p := range validatedParentLinks(parentLinks) {
124
+		if p.parentID != "" {
125
+			if err := l.setParentID(p.id, p.parentID); err != nil {
126
+				return err
127
+			}
128
+		}
120 129
 	}
121 130
 
122 131
 	return nil
123 132
 }
124 133
 
134
+func (l *tarexporter) setParentID(id, parentID image.ID) error {
135
+	img, err := l.is.Get(id)
136
+	if err != nil {
137
+		return err
138
+	}
139
+	parent, err := l.is.Get(parentID)
140
+	if err != nil {
141
+		return err
142
+	}
143
+	if !checkValidParent(img, parent) {
144
+		return fmt.Errorf("image %v is not a valid parent for %v", parent.ID, img.ID)
145
+	}
146
+	return l.is.SetParent(id, parentID)
147
+}
148
+
125 149
 func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, progressOutput progress.Output) (layer.Layer, error) {
126 150
 	rawTar, err := os.Open(filename)
127 151
 	if err != nil {
... ...
@@ -309,3 +336,36 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str
309 309
 func safePath(base, path string) (string, error) {
310 310
 	return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
311 311
 }
312
+
313
+type parentLink struct {
314
+	id, parentID image.ID
315
+}
316
+
317
+func validatedParentLinks(pl []parentLink) (ret []parentLink) {
318
+mainloop:
319
+	for i, p := range pl {
320
+		ret = append(ret, p)
321
+		for _, p2 := range pl {
322
+			if p2.id == p.parentID && p2.id != p.id {
323
+				continue mainloop
324
+			}
325
+		}
326
+		ret[i].parentID = ""
327
+	}
328
+	return
329
+}
330
+
331
+func checkValidParent(img, parent *image.Image) bool {
332
+	if len(img.History) == 0 && len(parent.History) == 0 {
333
+		return true // having history is not mandatory
334
+	}
335
+	if len(img.History)-len(parent.History) != 1 {
336
+		return false
337
+	}
338
+	for i, h := range parent.History {
339
+		if !reflect.DeepEqual(h, img.History[i]) {
340
+			return false
341
+		}
342
+	}
343
+	return true
344
+}
... ...
@@ -128,6 +128,7 @@ func (s *saveSession) save(outStream io.Writer) error {
128 128
 	reposLegacy := make(map[string]map[string]string)
129 129
 
130 130
 	var manifest []manifestItem
131
+	var parentLinks []parentLink
131 132
 
132 133
 	for id, imageDescr := range s.images {
133 134
 		if err = s.saveImage(id); err != nil {
... ...
@@ -154,6 +155,15 @@ func (s *saveSession) save(outStream io.Writer) error {
154 154
 			RepoTags: repoTags,
155 155
 			Layers:   layers,
156 156
 		})
157
+
158
+		parentID, _ := s.is.GetParent(id)
159
+		parentLinks = append(parentLinks, parentLink{id, parentID})
160
+	}
161
+
162
+	for i, p := range validatedParentLinks(parentLinks) {
163
+		if p.parentID != "" {
164
+			manifest[i].Parent = p.parentID
165
+		}
157 166
 	}
158 167
 
159 168
 	if len(reposLegacy) > 0 {
... ...
@@ -18,6 +18,7 @@ type manifestItem struct {
18 18
 	Config   string
19 19
 	RepoTags []string
20 20
 	Layers   []string
21
+	Parent   image.ID `json:",omitempty"`
21 22
 }
22 23
 
23 24
 type tarexporter struct {
... ...
@@ -311,3 +311,42 @@ func (s *DockerSuite) TestLoadZeroSizeLayer(c *check.C) {
311 311
 
312 312
 	dockerCmd(c, "load", "-i", "fixtures/load/emptyLayer.tar")
313 313
 }
314
+
315
+func (s *DockerSuite) TestSaveLoadParents(c *check.C) {
316
+	testRequires(c, DaemonIsLinux)
317
+
318
+	makeImage := func(from string, addfile string) string {
319
+		var (
320
+			out string
321
+		)
322
+		out, _ = dockerCmd(c, "run", "-d", from, "touch", addfile)
323
+		cleanedContainerID := strings.TrimSpace(out)
324
+
325
+		out, _ = dockerCmd(c, "commit", cleanedContainerID)
326
+		imageID := strings.TrimSpace(out)
327
+
328
+		dockerCmd(c, "rm", cleanedContainerID)
329
+		return imageID
330
+	}
331
+
332
+	idFoo := makeImage("busybox", "foo")
333
+	idBar := makeImage(idFoo, "bar")
334
+
335
+	tmpDir, err := ioutil.TempDir("", "save-load-parents")
336
+	c.Assert(err, checker.IsNil)
337
+	defer os.RemoveAll(tmpDir)
338
+
339
+	c.Log("tmpdir", tmpDir)
340
+
341
+	outfile := filepath.Join(tmpDir, "out.tar")
342
+
343
+	dockerCmd(c, "save", "-o", outfile, idBar, idFoo)
344
+	dockerCmd(c, "rmi", idBar)
345
+	dockerCmd(c, "load", "-i", outfile)
346
+
347
+	inspectOut := inspectField(c, idBar, "Parent")
348
+	c.Assert(inspectOut, checker.Equals, idFoo)
349
+
350
+	inspectOut = inspectField(c, idFoo, "Parent")
351
+	c.Assert(inspectOut, checker.Equals, "")
352
+}