Browse code

Move image into sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby authored on 2014/03/08 10:36:47
Showing 15 changed files
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/dotcloud/docker/engine"
9 9
 	"github.com/dotcloud/docker/execdriver"
10 10
 	"github.com/dotcloud/docker/graphdriver"
11
+	"github.com/dotcloud/docker/image"
11 12
 	"github.com/dotcloud/docker/links"
12 13
 	"github.com/dotcloud/docker/nat"
13 14
 	"github.com/dotcloud/docker/runconfig"
... ...
@@ -992,7 +993,7 @@ func (container *Container) Changes() ([]archive.Change, error) {
992 992
 	return container.runtime.Changes(container)
993 993
 }
994 994
 
995
-func (container *Container) GetImage() (*Image, error) {
995
+func (container *Container) GetImage() (*image.Image, error) {
996 996
 	if container.runtime == nil {
997 997
 		return nil, fmt.Errorf("Can't get image of unregistered container")
998 998
 	}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"github.com/dotcloud/docker/archive"
6 6
 	"github.com/dotcloud/docker/dockerversion"
7 7
 	"github.com/dotcloud/docker/graphdriver"
8
+	"github.com/dotcloud/docker/image"
8 9
 	"github.com/dotcloud/docker/runconfig"
9 10
 	"github.com/dotcloud/docker/utils"
10 11
 	"io"
... ...
@@ -79,20 +80,20 @@ func (graph *Graph) Exists(id string) bool {
79 79
 }
80 80
 
81 81
 // Get returns the image with the given id, or an error if the image doesn't exist.
82
-func (graph *Graph) Get(name string) (*Image, error) {
82
+func (graph *Graph) Get(name string) (*image.Image, error) {
83 83
 	id, err := graph.idIndex.Get(name)
84 84
 	if err != nil {
85 85
 		return nil, err
86 86
 	}
87 87
 	// FIXME: return nil when the image doesn't exist, instead of an error
88
-	img, err := LoadImage(graph.imageRoot(id))
88
+	img, err := image.LoadImage(graph.ImageRoot(id))
89 89
 	if err != nil {
90 90
 		return nil, err
91 91
 	}
92 92
 	if img.ID != id {
93 93
 		return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
94 94
 	}
95
-	img.graph = graph
95
+	img.SetGraph(graph)
96 96
 
97 97
 	if img.Size < 0 {
98 98
 		rootfs, err := graph.driver.Get(img.ID)
... ...
@@ -119,7 +120,7 @@ func (graph *Graph) Get(name string) (*Image, error) {
119 119
 		}
120 120
 
121 121
 		img.Size = size
122
-		if err := img.SaveSize(graph.imageRoot(id)); err != nil {
122
+		if err := img.SaveSize(graph.ImageRoot(id)); err != nil {
123 123
 			return nil, err
124 124
 		}
125 125
 	}
... ...
@@ -127,9 +128,9 @@ func (graph *Graph) Get(name string) (*Image, error) {
127 127
 }
128 128
 
129 129
 // Create creates a new image and registers it in the graph.
130
-func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container, comment, author string, config *runconfig.Config) (*Image, error) {
131
-	img := &Image{
132
-		ID:            GenerateID(),
130
+func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container, comment, author string, config *runconfig.Config) (*image.Image, error) {
131
+	img := &image.Image{
132
+		ID:            utils.GenerateRandomID(),
133 133
 		Comment:       comment,
134 134
 		Created:       time.Now().UTC(),
135 135
 		DockerVersion: dockerversion.VERSION,
... ...
@@ -151,7 +152,7 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, container *Container
151 151
 
152 152
 // Register imports a pre-existing image into the graph.
153 153
 // FIXME: pass img as first argument
154
-func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, img *Image) (err error) {
154
+func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, img *image.Image) (err error) {
155 155
 	defer func() {
156 156
 		// If any error occurs, remove the new dir from the driver.
157 157
 		// Don't check for errors since the dir might not have been created.
... ...
@@ -160,7 +161,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i
160 160
 			graph.driver.Remove(img.ID)
161 161
 		}
162 162
 	}()
163
-	if err := ValidateID(img.ID); err != nil {
163
+	if err := utils.ValidateID(img.ID); err != nil {
164 164
 		return err
165 165
 	}
166 166
 	// (This is a convenience to save time. Race conditions are taken care of by os.Rename)
... ...
@@ -171,7 +172,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i
171 171
 	// Ensure that the image root does not exist on the filesystem
172 172
 	// when it is not registered in the graph.
173 173
 	// This is common when you switch from one graph driver to another
174
-	if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
174
+	if err := os.RemoveAll(graph.ImageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
175 175
 		return err
176 176
 	}
177 177
 
... ...
@@ -197,12 +198,12 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i
197 197
 		return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err)
198 198
 	}
199 199
 	defer graph.driver.Put(img.ID)
200
-	img.graph = graph
201
-	if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil {
200
+	img.SetGraph(graph)
201
+	if err := image.StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil {
202 202
 		return err
203 203
 	}
204 204
 	// Commit
205
-	if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
205
+	if err := os.Rename(tmp, graph.ImageRoot(img.ID)); err != nil {
206 206
 		return err
207 207
 	}
208 208
 	graph.idIndex.Add(img.ID)
... ...
@@ -233,7 +234,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
233 233
 
234 234
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
235 235
 func (graph *Graph) Mktemp(id string) (string, error) {
236
-	dir := path.Join(graph.Root, "_tmp", GenerateID())
236
+	dir := path.Join(graph.Root, "_tmp", utils.GenerateRandomID())
237 237
 	if err := os.MkdirAll(dir, 0700); err != nil {
238 238
 		return "", err
239 239
 	}
... ...
@@ -320,7 +321,7 @@ func (graph *Graph) Delete(name string) error {
320 320
 		return err
321 321
 	}
322 322
 	graph.idIndex.Delete(id)
323
-	err = os.Rename(graph.imageRoot(id), tmp)
323
+	err = os.Rename(graph.ImageRoot(id), tmp)
324 324
 	if err != nil {
325 325
 		return err
326 326
 	}
... ...
@@ -331,9 +332,9 @@ func (graph *Graph) Delete(name string) error {
331 331
 }
332 332
 
333 333
 // Map returns a list of all images in the graph, addressable by ID.
334
-func (graph *Graph) Map() (map[string]*Image, error) {
335
-	images := make(map[string]*Image)
336
-	err := graph.walkAll(func(image *Image) {
334
+func (graph *Graph) Map() (map[string]*image.Image, error) {
335
+	images := make(map[string]*image.Image)
336
+	err := graph.walkAll(func(image *image.Image) {
337 337
 		images[image.ID] = image
338 338
 	})
339 339
 	if err != nil {
... ...
@@ -344,7 +345,7 @@ func (graph *Graph) Map() (map[string]*Image, error) {
344 344
 
345 345
 // walkAll iterates over each image in the graph, and passes it to a handler.
346 346
 // The walking order is undetermined.
347
-func (graph *Graph) walkAll(handler func(*Image)) error {
347
+func (graph *Graph) walkAll(handler func(*image.Image)) error {
348 348
 	files, err := ioutil.ReadDir(graph.Root)
349 349
 	if err != nil {
350 350
 		return err
... ...
@@ -364,17 +365,17 @@ func (graph *Graph) walkAll(handler func(*Image)) error {
364 364
 // If an image of id ID has 3 children images, then the value for key ID
365 365
 // will be a list of 3 images.
366 366
 // If an image has no children, it will not have an entry in the table.
367
-func (graph *Graph) ByParent() (map[string][]*Image, error) {
368
-	byParent := make(map[string][]*Image)
369
-	err := graph.walkAll(func(image *Image) {
370
-		parent, err := graph.Get(image.Parent)
367
+func (graph *Graph) ByParent() (map[string][]*image.Image, error) {
368
+	byParent := make(map[string][]*image.Image)
369
+	err := graph.walkAll(func(img *image.Image) {
370
+		parent, err := graph.Get(img.Parent)
371 371
 		if err != nil {
372 372
 			return
373 373
 		}
374 374
 		if children, exists := byParent[parent.ID]; exists {
375
-			byParent[parent.ID] = append(children, image)
375
+			byParent[parent.ID] = append(children, img)
376 376
 		} else {
377
-			byParent[parent.ID] = []*Image{image}
377
+			byParent[parent.ID] = []*image.Image{img}
378 378
 		}
379 379
 	})
380 380
 	return byParent, err
... ...
@@ -382,13 +383,13 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
382 382
 
383 383
 // Heads returns all heads in the graph, keyed by id.
384 384
 // A head is an image which is not the parent of another image in the graph.
385
-func (graph *Graph) Heads() (map[string]*Image, error) {
386
-	heads := make(map[string]*Image)
385
+func (graph *Graph) Heads() (map[string]*image.Image, error) {
386
+	heads := make(map[string]*image.Image)
387 387
 	byParent, err := graph.ByParent()
388 388
 	if err != nil {
389 389
 		return nil, err
390 390
 	}
391
-	err = graph.walkAll(func(image *Image) {
391
+	err = graph.walkAll(func(image *image.Image) {
392 392
 		// If it's not in the byParent lookup table, then
393 393
 		// it's not a parent -> so it's a head!
394 394
 		if _, exists := byParent[image.ID]; !exists {
... ...
@@ -398,7 +399,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
398 398
 	return heads, err
399 399
 }
400 400
 
401
-func (graph *Graph) imageRoot(id string) string {
401
+func (graph *Graph) ImageRoot(id string) string {
402 402
 	return path.Join(graph.Root, id)
403 403
 }
404 404
 
405 405
deleted file mode 100644
... ...
@@ -1,318 +0,0 @@
1
-package docker
2
-
3
-import (
4
-	"crypto/rand"
5
-	"encoding/hex"
6
-	"encoding/json"
7
-	"fmt"
8
-	"github.com/dotcloud/docker/archive"
9
-	"github.com/dotcloud/docker/graphdriver"
10
-	"github.com/dotcloud/docker/runconfig"
11
-	"github.com/dotcloud/docker/utils"
12
-	"io"
13
-	"io/ioutil"
14
-	"os"
15
-	"path"
16
-	"strconv"
17
-	"strings"
18
-	"time"
19
-)
20
-
21
-type Image struct {
22
-	ID              string            `json:"id"`
23
-	Parent          string            `json:"parent,omitempty"`
24
-	Comment         string            `json:"comment,omitempty"`
25
-	Created         time.Time         `json:"created"`
26
-	Container       string            `json:"container,omitempty"`
27
-	ContainerConfig runconfig.Config  `json:"container_config,omitempty"`
28
-	DockerVersion   string            `json:"docker_version,omitempty"`
29
-	Author          string            `json:"author,omitempty"`
30
-	Config          *runconfig.Config `json:"config,omitempty"`
31
-	Architecture    string            `json:"architecture,omitempty"`
32
-	OS              string            `json:"os,omitempty"`
33
-	graph           *Graph
34
-	Size            int64
35
-}
36
-
37
-func LoadImage(root string) (*Image, error) {
38
-	// Load the json data
39
-	jsonData, err := ioutil.ReadFile(jsonPath(root))
40
-	if err != nil {
41
-		return nil, err
42
-	}
43
-	img := &Image{}
44
-
45
-	if err := json.Unmarshal(jsonData, img); err != nil {
46
-		return nil, err
47
-	}
48
-	if err := ValidateID(img.ID); err != nil {
49
-		return nil, err
50
-	}
51
-
52
-	if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
53
-		if !os.IsNotExist(err) {
54
-			return nil, err
55
-		}
56
-		// If the layersize file does not exist then set the size to a negative number
57
-		// because a layer size of 0 (zero) is valid
58
-		img.Size = -1
59
-	} else {
60
-		size, err := strconv.Atoi(string(buf))
61
-		if err != nil {
62
-			return nil, err
63
-		}
64
-		img.Size = int64(size)
65
-	}
66
-
67
-	return img, nil
68
-}
69
-
70
-func StoreImage(img *Image, jsonData []byte, layerData archive.ArchiveReader, root, layer string) error {
71
-	// Store the layer
72
-	var (
73
-		size   int64
74
-		err    error
75
-		driver = img.graph.driver
76
-	)
77
-	if err := os.MkdirAll(layer, 0755); err != nil {
78
-		return err
79
-	}
80
-
81
-	// If layerData is not nil, unpack it into the new layer
82
-	if layerData != nil {
83
-		if differ, ok := driver.(graphdriver.Differ); ok {
84
-			if err := differ.ApplyDiff(img.ID, layerData); err != nil {
85
-				return err
86
-			}
87
-
88
-			if size, err = differ.DiffSize(img.ID); err != nil {
89
-				return err
90
-			}
91
-		} else {
92
-			start := time.Now().UTC()
93
-			utils.Debugf("Start untar layer")
94
-			if err := archive.ApplyLayer(layer, layerData); err != nil {
95
-				return err
96
-			}
97
-			utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
98
-
99
-			if img.Parent == "" {
100
-				if size, err = utils.TreeSize(layer); err != nil {
101
-					return err
102
-				}
103
-			} else {
104
-				parent, err := driver.Get(img.Parent)
105
-				if err != nil {
106
-					return err
107
-				}
108
-				defer driver.Put(img.Parent)
109
-				changes, err := archive.ChangesDirs(layer, parent)
110
-				if err != nil {
111
-					return err
112
-				}
113
-				size = archive.ChangesSize(layer, changes)
114
-			}
115
-		}
116
-	}
117
-
118
-	img.Size = size
119
-	if err := img.SaveSize(root); err != nil {
120
-		return err
121
-	}
122
-
123
-	// If raw json is provided, then use it
124
-	if jsonData != nil {
125
-		if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
126
-			return err
127
-		}
128
-	} else {
129
-		if jsonData, err = json.Marshal(img); err != nil {
130
-			return err
131
-		}
132
-		if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
133
-			return err
134
-		}
135
-	}
136
-	return nil
137
-}
138
-
139
-// SaveSize stores the current `size` value of `img` in the directory `root`.
140
-func (img *Image) SaveSize(root string) error {
141
-	if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil {
142
-		return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err)
143
-	}
144
-	return nil
145
-}
146
-
147
-func jsonPath(root string) string {
148
-	return path.Join(root, "json")
149
-}
150
-
151
-// TarLayer returns a tar archive of the image's filesystem layer.
152
-func (img *Image) TarLayer() (arch archive.Archive, err error) {
153
-	if img.graph == nil {
154
-		return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
155
-	}
156
-	driver := img.graph.driver
157
-	if differ, ok := driver.(graphdriver.Differ); ok {
158
-		return differ.Diff(img.ID)
159
-	}
160
-
161
-	imgFs, err := driver.Get(img.ID)
162
-	if err != nil {
163
-		return nil, err
164
-	}
165
-
166
-	defer func() {
167
-		if err != nil {
168
-			driver.Put(img.ID)
169
-		}
170
-	}()
171
-
172
-	if img.Parent == "" {
173
-		archive, err := archive.Tar(imgFs, archive.Uncompressed)
174
-		if err != nil {
175
-			return nil, err
176
-		}
177
-		return utils.NewReadCloserWrapper(archive, func() error {
178
-			err := archive.Close()
179
-			driver.Put(img.ID)
180
-			return err
181
-		}), nil
182
-	}
183
-
184
-	parentFs, err := driver.Get(img.Parent)
185
-	if err != nil {
186
-		return nil, err
187
-	}
188
-	defer driver.Put(img.Parent)
189
-	changes, err := archive.ChangesDirs(imgFs, parentFs)
190
-	if err != nil {
191
-		return nil, err
192
-	}
193
-	archive, err := archive.ExportChanges(imgFs, changes)
194
-	if err != nil {
195
-		return nil, err
196
-	}
197
-	return utils.NewReadCloserWrapper(archive, func() error {
198
-		err := archive.Close()
199
-		driver.Put(img.ID)
200
-		return err
201
-	}), nil
202
-}
203
-
204
-func ValidateID(id string) error {
205
-	if id == "" {
206
-		return fmt.Errorf("Image id can't be empty")
207
-	}
208
-	if strings.Contains(id, ":") {
209
-		return fmt.Errorf("Invalid character in image id: ':'")
210
-	}
211
-	return nil
212
-}
213
-
214
-func GenerateID() string {
215
-	for {
216
-		id := make([]byte, 32)
217
-		if _, err := io.ReadFull(rand.Reader, id); err != nil {
218
-			panic(err) // This shouldn't happen
219
-		}
220
-		value := hex.EncodeToString(id)
221
-		// if we try to parse the truncated for as an int and we don't have
222
-		// an error then the value is all numberic and causes issues when
223
-		// used as a hostname. ref #3869
224
-		if _, err := strconv.Atoi(utils.TruncateID(value)); err == nil {
225
-			continue
226
-		}
227
-		return value
228
-	}
229
-}
230
-
231
-// Image includes convenience proxy functions to its graph
232
-// These functions will return an error if the image is not registered
233
-// (ie. if image.graph == nil)
234
-func (img *Image) History() ([]*Image, error) {
235
-	var parents []*Image
236
-	if err := img.WalkHistory(
237
-		func(img *Image) error {
238
-			parents = append(parents, img)
239
-			return nil
240
-		},
241
-	); err != nil {
242
-		return nil, err
243
-	}
244
-	return parents, nil
245
-}
246
-
247
-func (img *Image) WalkHistory(handler func(*Image) error) (err error) {
248
-	currentImg := img
249
-	for currentImg != nil {
250
-		if handler != nil {
251
-			if err := handler(currentImg); err != nil {
252
-				return err
253
-			}
254
-		}
255
-		currentImg, err = currentImg.GetParent()
256
-		if err != nil {
257
-			return fmt.Errorf("Error while getting parent image: %v", err)
258
-		}
259
-	}
260
-	return nil
261
-}
262
-
263
-func (img *Image) GetParent() (*Image, error) {
264
-	if img.Parent == "" {
265
-		return nil, nil
266
-	}
267
-	if img.graph == nil {
268
-		return nil, fmt.Errorf("Can't lookup parent of unregistered image")
269
-	}
270
-	return img.graph.Get(img.Parent)
271
-}
272
-
273
-func (img *Image) root() (string, error) {
274
-	if img.graph == nil {
275
-		return "", fmt.Errorf("Can't lookup root of unregistered image")
276
-	}
277
-	return img.graph.imageRoot(img.ID), nil
278
-}
279
-
280
-func (img *Image) getParentsSize(size int64) int64 {
281
-	parentImage, err := img.GetParent()
282
-	if err != nil || parentImage == nil {
283
-		return size
284
-	}
285
-	size += parentImage.Size
286
-	return parentImage.getParentsSize(size)
287
-}
288
-
289
-// Depth returns the number of parents for a
290
-// current image
291
-func (img *Image) Depth() (int, error) {
292
-	var (
293
-		count  = 0
294
-		parent = img
295
-		err    error
296
-	)
297
-
298
-	for parent != nil {
299
-		count++
300
-		parent, err = parent.GetParent()
301
-		if err != nil {
302
-			return -1, err
303
-		}
304
-	}
305
-	return count, nil
306
-}
307
-
308
-// Build an Image object from raw json data
309
-func NewImgJSON(src []byte) (*Image, error) {
310
-	ret := &Image{}
311
-
312
-	utils.Debugf("Json string: {%s}", src)
313
-	// FIXME: Is there a cleaner way to "purify" the input json?
314
-	if err := json.Unmarshal(src, ret); err != nil {
315
-		return nil, err
316
-	}
317
-	return ret, nil
318
-}
319 1
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+package image
1
+
2
+import (
3
+	"github.com/dotcloud/docker/graphdriver"
4
+)
5
+
6
+type Graph interface {
7
+	Get(id string) (*Image, error)
8
+	ImageRoot(id string) string
9
+	Driver() graphdriver.Driver
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,292 @@
0
+package image
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"github.com/dotcloud/docker/archive"
6
+	"github.com/dotcloud/docker/graphdriver"
7
+	"github.com/dotcloud/docker/runconfig"
8
+	"github.com/dotcloud/docker/utils"
9
+	"io/ioutil"
10
+	"os"
11
+	"path"
12
+	"strconv"
13
+	"time"
14
+)
15
+
16
+type Image struct {
17
+	ID              string            `json:"id"`
18
+	Parent          string            `json:"parent,omitempty"`
19
+	Comment         string            `json:"comment,omitempty"`
20
+	Created         time.Time         `json:"created"`
21
+	Container       string            `json:"container,omitempty"`
22
+	ContainerConfig runconfig.Config  `json:"container_config,omitempty"`
23
+	DockerVersion   string            `json:"docker_version,omitempty"`
24
+	Author          string            `json:"author,omitempty"`
25
+	Config          *runconfig.Config `json:"config,omitempty"`
26
+	Architecture    string            `json:"architecture,omitempty"`
27
+	OS              string            `json:"os,omitempty"`
28
+	Size            int64
29
+
30
+	graph Graph
31
+}
32
+
33
+func LoadImage(root string) (*Image, error) {
34
+	// Load the json data
35
+	jsonData, err := ioutil.ReadFile(jsonPath(root))
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+	img := &Image{}
40
+
41
+	if err := json.Unmarshal(jsonData, img); err != nil {
42
+		return nil, err
43
+	}
44
+	if err := utils.ValidateID(img.ID); err != nil {
45
+		return nil, err
46
+	}
47
+
48
+	if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
49
+		if !os.IsNotExist(err) {
50
+			return nil, err
51
+		}
52
+		// If the layersize file does not exist then set the size to a negative number
53
+		// because a layer size of 0 (zero) is valid
54
+		img.Size = -1
55
+	} else {
56
+		size, err := strconv.Atoi(string(buf))
57
+		if err != nil {
58
+			return nil, err
59
+		}
60
+		img.Size = int64(size)
61
+	}
62
+
63
+	return img, nil
64
+}
65
+
66
+func StoreImage(img *Image, jsonData []byte, layerData archive.ArchiveReader, root, layer string) error {
67
+	// Store the layer
68
+	var (
69
+		size   int64
70
+		err    error
71
+		driver = img.graph.Driver()
72
+	)
73
+	if err := os.MkdirAll(layer, 0755); err != nil {
74
+		return err
75
+	}
76
+
77
+	// If layerData is not nil, unpack it into the new layer
78
+	if layerData != nil {
79
+		if differ, ok := driver.(graphdriver.Differ); ok {
80
+			if err := differ.ApplyDiff(img.ID, layerData); err != nil {
81
+				return err
82
+			}
83
+
84
+			if size, err = differ.DiffSize(img.ID); err != nil {
85
+				return err
86
+			}
87
+		} else {
88
+			start := time.Now().UTC()
89
+			utils.Debugf("Start untar layer")
90
+			if err := archive.ApplyLayer(layer, layerData); err != nil {
91
+				return err
92
+			}
93
+			utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
94
+
95
+			if img.Parent == "" {
96
+				if size, err = utils.TreeSize(layer); err != nil {
97
+					return err
98
+				}
99
+			} else {
100
+				parent, err := driver.Get(img.Parent)
101
+				if err != nil {
102
+					return err
103
+				}
104
+				defer driver.Put(img.Parent)
105
+				changes, err := archive.ChangesDirs(layer, parent)
106
+				if err != nil {
107
+					return err
108
+				}
109
+				size = archive.ChangesSize(layer, changes)
110
+			}
111
+		}
112
+	}
113
+
114
+	img.Size = size
115
+	if err := img.SaveSize(root); err != nil {
116
+		return err
117
+	}
118
+
119
+	// If raw json is provided, then use it
120
+	if jsonData != nil {
121
+		if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
122
+			return err
123
+		}
124
+	} else {
125
+		if jsonData, err = json.Marshal(img); err != nil {
126
+			return err
127
+		}
128
+		if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
129
+			return err
130
+		}
131
+	}
132
+	return nil
133
+}
134
+
135
+func (img *Image) SetGraph(graph Graph) {
136
+	img.graph = graph
137
+}
138
+
139
+// SaveSize stores the current `size` value of `img` in the directory `root`.
140
+func (img *Image) SaveSize(root string) error {
141
+	if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil {
142
+		return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err)
143
+	}
144
+	return nil
145
+}
146
+
147
+func jsonPath(root string) string {
148
+	return path.Join(root, "json")
149
+}
150
+
151
+// TarLayer returns a tar archive of the image's filesystem layer.
152
+func (img *Image) TarLayer() (arch archive.Archive, err error) {
153
+	if img.graph == nil {
154
+		return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
155
+	}
156
+	driver := img.graph.Driver()
157
+	if differ, ok := driver.(graphdriver.Differ); ok {
158
+		return differ.Diff(img.ID)
159
+	}
160
+
161
+	imgFs, err := driver.Get(img.ID)
162
+	if err != nil {
163
+		return nil, err
164
+	}
165
+
166
+	defer func() {
167
+		if err != nil {
168
+			driver.Put(img.ID)
169
+		}
170
+	}()
171
+
172
+	if img.Parent == "" {
173
+		archive, err := archive.Tar(imgFs, archive.Uncompressed)
174
+		if err != nil {
175
+			return nil, err
176
+		}
177
+		return utils.NewReadCloserWrapper(archive, func() error {
178
+			err := archive.Close()
179
+			driver.Put(img.ID)
180
+			return err
181
+		}), nil
182
+	}
183
+
184
+	parentFs, err := driver.Get(img.Parent)
185
+	if err != nil {
186
+		return nil, err
187
+	}
188
+	defer driver.Put(img.Parent)
189
+	changes, err := archive.ChangesDirs(imgFs, parentFs)
190
+	if err != nil {
191
+		return nil, err
192
+	}
193
+	archive, err := archive.ExportChanges(imgFs, changes)
194
+	if err != nil {
195
+		return nil, err
196
+	}
197
+	return utils.NewReadCloserWrapper(archive, func() error {
198
+		err := archive.Close()
199
+		driver.Put(img.ID)
200
+		return err
201
+	}), nil
202
+}
203
+
204
+// Image includes convenience proxy functions to its graph
205
+// These functions will return an error if the image is not registered
206
+// (ie. if image.graph == nil)
207
+func (img *Image) History() ([]*Image, error) {
208
+	var parents []*Image
209
+	if err := img.WalkHistory(
210
+		func(img *Image) error {
211
+			parents = append(parents, img)
212
+			return nil
213
+		},
214
+	); err != nil {
215
+		return nil, err
216
+	}
217
+	return parents, nil
218
+}
219
+
220
+func (img *Image) WalkHistory(handler func(*Image) error) (err error) {
221
+	currentImg := img
222
+	for currentImg != nil {
223
+		if handler != nil {
224
+			if err := handler(currentImg); err != nil {
225
+				return err
226
+			}
227
+		}
228
+		currentImg, err = currentImg.GetParent()
229
+		if err != nil {
230
+			return fmt.Errorf("Error while getting parent image: %v", err)
231
+		}
232
+	}
233
+	return nil
234
+}
235
+
236
+func (img *Image) GetParent() (*Image, error) {
237
+	if img.Parent == "" {
238
+		return nil, nil
239
+	}
240
+	if img.graph == nil {
241
+		return nil, fmt.Errorf("Can't lookup parent of unregistered image")
242
+	}
243
+	return img.graph.Get(img.Parent)
244
+}
245
+
246
+func (img *Image) root() (string, error) {
247
+	if img.graph == nil {
248
+		return "", fmt.Errorf("Can't lookup root of unregistered image")
249
+	}
250
+	return img.graph.ImageRoot(img.ID), nil
251
+}
252
+
253
+func (img *Image) GetParentsSize(size int64) int64 {
254
+	parentImage, err := img.GetParent()
255
+	if err != nil || parentImage == nil {
256
+		return size
257
+	}
258
+	size += parentImage.Size
259
+	return parentImage.GetParentsSize(size)
260
+}
261
+
262
+// Depth returns the number of parents for a
263
+// current image
264
+func (img *Image) Depth() (int, error) {
265
+	var (
266
+		count  = 0
267
+		parent = img
268
+		err    error
269
+	)
270
+
271
+	for parent != nil {
272
+		count++
273
+		parent, err = parent.GetParent()
274
+		if err != nil {
275
+			return -1, err
276
+		}
277
+	}
278
+	return count, nil
279
+}
280
+
281
+// Build an Image object from raw json data
282
+func NewImgJSON(src []byte) (*Image, error) {
283
+	ret := &Image{}
284
+
285
+	utils.Debugf("Json string: {%s}", src)
286
+	// FIXME: Is there a cleaner way to "purify" the input json?
287
+	if err := json.Unmarshal(src, ret); err != nil {
288
+		return nil, err
289
+	}
290
+	return ret, nil
291
+}
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"github.com/dotcloud/docker/api"
10 10
 	"github.com/dotcloud/docker/dockerversion"
11 11
 	"github.com/dotcloud/docker/engine"
12
+	"github.com/dotcloud/docker/image"
12 13
 	"github.com/dotcloud/docker/runconfig"
13 14
 	"github.com/dotcloud/docker/utils"
14 15
 	"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
... ...
@@ -287,7 +288,7 @@ func TestGetImagesByName(t *testing.T) {
287 287
 	}
288 288
 	assertHttpNotError(r, t)
289 289
 
290
-	img := &docker.Image{}
290
+	img := &image.Image{}
291 291
 	if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
292 292
 		t.Fatal(err)
293 293
 	}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"github.com/dotcloud/docker"
6 6
 	"github.com/dotcloud/docker/archive"
7 7
 	"github.com/dotcloud/docker/engine"
8
+	"github.com/dotcloud/docker/image"
8 9
 	"github.com/dotcloud/docker/utils"
9 10
 	"io/ioutil"
10 11
 	"net"
... ...
@@ -350,7 +351,7 @@ func TestBuild(t *testing.T) {
350 350
 	}
351 351
 }
352 352
 
353
-func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*docker.Image, error) {
353
+func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, useCache bool) (*image.Image, error) {
354 354
 	if eng == nil {
355 355
 		eng = NewTestEngine(t)
356 356
 		runtime := mkRuntimeFromEngine(eng, t)
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/dotcloud/docker"
7 7
 	"github.com/dotcloud/docker/api"
8 8
 	"github.com/dotcloud/docker/engine"
9
+	"github.com/dotcloud/docker/image"
9 10
 	"github.com/dotcloud/docker/pkg/term"
10 11
 	"github.com/dotcloud/docker/utils"
11 12
 	"io"
... ...
@@ -902,7 +903,7 @@ func TestImagesTree(t *testing.T) {
902 902
 	})
903 903
 }
904 904
 
905
-func buildTestImages(t *testing.T, eng *engine.Engine) *docker.Image {
905
+func buildTestImages(t *testing.T, eng *engine.Engine) *image.Image {
906 906
 
907 907
 	var testBuilder = testContextTemplate{
908 908
 		`
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/dotcloud/docker/archive"
7 7
 	"github.com/dotcloud/docker/dockerversion"
8 8
 	"github.com/dotcloud/docker/graphdriver"
9
+	"github.com/dotcloud/docker/image"
9 10
 	"github.com/dotcloud/docker/utils"
10 11
 	"io"
11 12
 	"io/ioutil"
... ...
@@ -67,8 +68,8 @@ func TestInterruptedRegister(t *testing.T) {
67 67
 	graph, _ := tempGraph(t)
68 68
 	defer nukeGraph(graph)
69 69
 	badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
70
-	image := &docker.Image{
71
-		ID:      docker.GenerateID(),
70
+	image := &image.Image{
71
+		ID:      utils.GenerateRandomID(),
72 72
 		Comment: "testing",
73 73
 		Created: time.Now(),
74 74
 	}
... ...
@@ -96,18 +97,18 @@ func TestGraphCreate(t *testing.T) {
96 96
 	if err != nil {
97 97
 		t.Fatal(err)
98 98
 	}
99
-	image, err := graph.Create(archive, nil, "Testing", "", nil)
99
+	img, err := graph.Create(archive, nil, "Testing", "", nil)
100 100
 	if err != nil {
101 101
 		t.Fatal(err)
102 102
 	}
103
-	if err := docker.ValidateID(image.ID); err != nil {
103
+	if err := utils.ValidateID(img.ID); err != nil {
104 104
 		t.Fatal(err)
105 105
 	}
106
-	if image.Comment != "Testing" {
107
-		t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
106
+	if img.Comment != "Testing" {
107
+		t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", img.Comment)
108 108
 	}
109
-	if image.DockerVersion != dockerversion.VERSION {
110
-		t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, image.DockerVersion)
109
+	if img.DockerVersion != dockerversion.VERSION {
110
+		t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, img.DockerVersion)
111 111
 	}
112 112
 	images, err := graph.Map()
113 113
 	if err != nil {
... ...
@@ -115,8 +116,8 @@ func TestGraphCreate(t *testing.T) {
115 115
 	} else if l := len(images); l != 1 {
116 116
 		t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
117 117
 	}
118
-	if images[image.ID] == nil {
119
-		t.Fatalf("Could not find image with id %s", image.ID)
118
+	if images[img.ID] == nil {
119
+		t.Fatalf("Could not find image with id %s", img.ID)
120 120
 	}
121 121
 }
122 122
 
... ...
@@ -127,8 +128,8 @@ func TestRegister(t *testing.T) {
127 127
 	if err != nil {
128 128
 		t.Fatal(err)
129 129
 	}
130
-	image := &docker.Image{
131
-		ID:      docker.GenerateID(),
130
+	image := &image.Image{
131
+		ID:      utils.GenerateRandomID(),
132 132
 		Comment: "testing",
133 133
 		Created: time.Now(),
134 134
 	}
... ...
@@ -164,7 +165,7 @@ func TestDeletePrefix(t *testing.T) {
164 164
 	assertNImages(graph, t, 0)
165 165
 }
166 166
 
167
-func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image {
167
+func createTestImage(graph *docker.Graph, t *testing.T) *image.Image {
168 168
 	archive, err := fakeTar()
169 169
 	if err != nil {
170 170
 		t.Fatal(err)
... ...
@@ -243,20 +244,20 @@ func TestByParent(t *testing.T) {
243 243
 
244 244
 	graph, _ := tempGraph(t)
245 245
 	defer nukeGraph(graph)
246
-	parentImage := &docker.Image{
247
-		ID:      docker.GenerateID(),
246
+	parentImage := &image.Image{
247
+		ID:      utils.GenerateRandomID(),
248 248
 		Comment: "parent",
249 249
 		Created: time.Now(),
250 250
 		Parent:  "",
251 251
 	}
252
-	childImage1 := &docker.Image{
253
-		ID:      docker.GenerateID(),
252
+	childImage1 := &image.Image{
253
+		ID:      utils.GenerateRandomID(),
254 254
 		Comment: "child1",
255 255
 		Created: time.Now(),
256 256
 		Parent:  parentImage.ID,
257 257
 	}
258
-	childImage2 := &docker.Image{
259
-		ID:      docker.GenerateID(),
258
+	childImage2 := &image.Image{
259
+		ID:      utils.GenerateRandomID(),
260 260
 		Comment: "child2",
261 261
 		Created: time.Now(),
262 262
 		Parent:  parentImage.ID,
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"fmt"
6 6
 	"github.com/dotcloud/docker"
7 7
 	"github.com/dotcloud/docker/engine"
8
+	"github.com/dotcloud/docker/image"
8 9
 	"github.com/dotcloud/docker/nat"
9 10
 	"github.com/dotcloud/docker/runconfig"
10 11
 	"github.com/dotcloud/docker/sysinit"
... ...
@@ -172,7 +173,7 @@ func spawnGlobalDaemon() {
172 172
 
173 173
 // FIXME: test that ImagePull(json=true) send correct json output
174 174
 
175
-func GetTestImage(runtime *docker.Runtime) *docker.Image {
175
+func GetTestImage(runtime *docker.Runtime) *image.Image {
176 176
 	imgs, err := runtime.Graph().Map()
177 177
 	if err != nil {
178 178
 		log.Fatalf("Unable to get the test image: %s", err)
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	_ "github.com/dotcloud/docker/graphdriver/btrfs"
16 16
 	_ "github.com/dotcloud/docker/graphdriver/devmapper"
17 17
 	_ "github.com/dotcloud/docker/graphdriver/vfs"
18
+	"github.com/dotcloud/docker/image"
18 19
 	_ "github.com/dotcloud/docker/networkdriver/lxc"
19 20
 	"github.com/dotcloud/docker/networkdriver/portallocator"
20 21
 	"github.com/dotcloud/docker/pkg/graphdb"
... ...
@@ -396,7 +397,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
396 396
 	}
397 397
 
398 398
 	// Generate id
399
-	id := GenerateID()
399
+	id := utils.GenerateRandomID()
400 400
 
401 401
 	if name == "" {
402 402
 		name, err = generateRandomName(runtime)
... ...
@@ -539,7 +540,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
539 539
 
540 540
 // Commit creates a new filesystem image from the current state of a container.
541 541
 // The image can optionally be tagged into a repository
542
-func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*Image, error) {
542
+func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) {
543 543
 	// FIXME: freeze the container before copying it to avoid data corruption?
544 544
 	// FIXME: this shouldn't be in commands.
545 545
 	if err := container.Mount(); err != nil {
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/dotcloud/docker/daemonconfig"
9 9
 	"github.com/dotcloud/docker/dockerversion"
10 10
 	"github.com/dotcloud/docker/engine"
11
+	"github.com/dotcloud/docker/image"
11 12
 	"github.com/dotcloud/docker/pkg/graphdb"
12 13
 	"github.com/dotcloud/docker/registry"
13 14
 	"github.com/dotcloud/docker/runconfig"
... ...
@@ -362,8 +363,8 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status {
362 362
 	return engine.StatusOK
363 363
 }
364 364
 
365
-func (srv *Server) exportImage(image *Image, tempdir string) error {
366
-	for i := image; i != nil; {
365
+func (srv *Server) exportImage(img *image.Image, tempdir string) error {
366
+	for i := img; i != nil; {
367 367
 		// temporary directory
368 368
 		tmpImageDir := path.Join(tempdir, i.ID)
369 369
 		if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
... ...
@@ -580,7 +581,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
580 580
 			utils.Debugf("Error reading embedded tar", err)
581 581
 			return err
582 582
 		}
583
-		img, err := NewImgJSON(imageJson)
583
+		img, err := image.NewImgJSON(imageJson)
584 584
 		if err != nil {
585 585
 			utils.Debugf("Error unmarshalling json", err)
586 586
 			return err
... ...
@@ -690,7 +691,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status {
690 690
 	job.Stdout.Write([]byte("digraph docker {\n"))
691 691
 
692 692
 	var (
693
-		parentImage *Image
693
+		parentImage *image.Image
694 694
 		err         error
695 695
 	)
696 696
 	for _, image := range images {
... ...
@@ -722,7 +723,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status {
722 722
 
723 723
 func (srv *Server) Images(job *engine.Job) engine.Status {
724 724
 	var (
725
-		allImages map[string]*Image
725
+		allImages map[string]*image.Image
726 726
 		err       error
727 727
 	)
728 728
 	if job.GetenvBool("all") {
... ...
@@ -757,7 +758,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status {
757 757
 				out.Set("Id", image.ID)
758 758
 				out.SetInt64("Created", image.Created.Unix())
759 759
 				out.SetInt64("Size", image.Size)
760
-				out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size)
760
+				out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
761 761
 				lookup[id] = out
762 762
 			}
763 763
 
... ...
@@ -778,7 +779,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status {
778 778
 			out.Set("Id", image.ID)
779 779
 			out.SetInt64("Created", image.Created.Unix())
780 780
 			out.SetInt64("Size", image.Size)
781
-			out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size)
781
+			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
782 782
 			outs.Add(out)
783 783
 		}
784 784
 	}
... ...
@@ -838,7 +839,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status {
838 838
 		return job.Errorf("Usage: %s IMAGE", job.Name)
839 839
 	}
840 840
 	name := job.Args[0]
841
-	image, err := srv.runtime.repositories.LookupImage(name)
841
+	foundImage, err := srv.runtime.repositories.LookupImage(name)
842 842
 	if err != nil {
843 843
 		return job.Error(err)
844 844
 	}
... ...
@@ -855,7 +856,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status {
855 855
 	}
856 856
 
857 857
 	outs := engine.NewTable("Created", 0)
858
-	err = image.WalkHistory(func(img *Image) error {
858
+	err = foundImage.WalkHistory(func(img *image.Image) error {
859 859
 		out := &engine.Env{}
860 860
 		out.Set("Id", img.ID)
861 861
 		out.SetInt64("Created", img.Created.Unix())
... ...
@@ -1098,7 +1099,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
1098 1098
 				// FIXME: Keep going in case of error?
1099 1099
 				return err
1100 1100
 			}
1101
-			img, err := NewImgJSON(imgJSON)
1101
+			img, err := image.NewImgJSON(imgJSON)
1102 1102
 			if err != nil {
1103 1103
 				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
1104 1104
 				return fmt.Errorf("Failed to parse json: %s", err)
... ...
@@ -1946,7 +1947,7 @@ func (srv *Server) canDeleteImage(imgID string) error {
1946 1946
 			return err
1947 1947
 		}
1948 1948
 
1949
-		if err := parent.WalkHistory(func(p *Image) error {
1949
+		if err := parent.WalkHistory(func(p *image.Image) error {
1950 1950
 			if imgID == p.ID {
1951 1951
 				return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", utils.TruncateID(imgID), utils.TruncateID(container.ID))
1952 1952
 			}
... ...
@@ -1958,7 +1959,7 @@ func (srv *Server) canDeleteImage(imgID string) error {
1958 1958
 	return nil
1959 1959
 }
1960 1960
 
1961
-func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Image, error) {
1961
+func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) {
1962 1962
 
1963 1963
 	// Retrieve all images
1964 1964
 	images, err := srv.runtime.graph.Map()
... ...
@@ -1976,7 +1977,7 @@ func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Imag
1976 1976
 	}
1977 1977
 
1978 1978
 	// Loop on the children of the given image and check the config
1979
-	var match *Image
1979
+	var match *image.Image
1980 1980
 	for elem := range imageMap[imgID] {
1981 1981
 		img, err := srv.runtime.graph.Get(elem)
1982 1982
 		if err != nil {
... ...
@@ -2242,7 +2243,7 @@ func (srv *Server) ContainerInspect(name string) (*Container, error) {
2242 2242
 	return nil, fmt.Errorf("No such container: %s", name)
2243 2243
 }
2244 2244
 
2245
-func (srv *Server) ImageInspect(name string) (*Image, error) {
2245
+func (srv *Server) ImageInspect(name string) (*image.Image, error) {
2246 2246
 	if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil {
2247 2247
 		return image, nil
2248 2248
 	}
... ...
@@ -3,6 +3,7 @@ package docker
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/image"
6 7
 	"github.com/dotcloud/docker/utils"
7 8
 	"io/ioutil"
8 9
 	"os"
... ...
@@ -65,7 +66,7 @@ func (store *TagStore) Reload() error {
65 65
 	return nil
66 66
 }
67 67
 
68
-func (store *TagStore) LookupImage(name string) (*Image, error) {
68
+func (store *TagStore) LookupImage(name string) (*image.Image, error) {
69 69
 	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
70 70
 	// (so we can pass all errors here)
71 71
 	repos, tag := utils.ParseRepositoryTag(name)
... ...
@@ -195,7 +196,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
195 195
 	return nil, nil
196 196
 }
197 197
 
198
-func (store *TagStore) GetImage(repoName, tagOrID string) (*Image, error) {
198
+func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) {
199 199
 	repo, err := store.Get(repoName)
200 200
 	if err != nil {
201 201
 		return nil, err
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"github.com/dotcloud/docker/graphdriver"
5
+	"github.com/dotcloud/docker/image"
5 6
 	"github.com/dotcloud/docker/utils"
6 7
 	"os"
7 8
 	"path"
... ...
@@ -30,7 +31,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
30 30
 	if err != nil {
31 31
 		t.Fatal(err)
32 32
 	}
33
-	img := &Image{ID: testImageID}
33
+	img := &image.Image{ID: testImageID}
34 34
 	// FIXME: this fails on Darwin with:
35 35
 	// tags_unit_test.go:36: mkdir /var/folders/7g/b3ydb5gx4t94ndr_cljffbt80000gq/T/docker-test569b-tRunner-075013689/vfs/dir/foo/etc/postgres: permission denied
36 36
 	if err := graph.Register(nil, archive, img); err != nil {
... ...
@@ -2,6 +2,7 @@ package utils
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"crypto/rand"
5 6
 	"crypto/sha1"
6 7
 	"crypto/sha256"
7 8
 	"encoding/hex"
... ...
@@ -493,6 +494,34 @@ func TruncateID(id string) string {
493 493
 	return id[:shortLen]
494 494
 }
495 495
 
496
+// GenerateRandomID returns an unique id
497
+func GenerateRandomID() string {
498
+	for {
499
+		id := make([]byte, 32)
500
+		if _, err := io.ReadFull(rand.Reader, id); err != nil {
501
+			panic(err) // This shouldn't happen
502
+		}
503
+		value := hex.EncodeToString(id)
504
+		// if we try to parse the truncated for as an int and we don't have
505
+		// an error then the value is all numberic and causes issues when
506
+		// used as a hostname. ref #3869
507
+		if _, err := strconv.Atoi(TruncateID(value)); err == nil {
508
+			continue
509
+		}
510
+		return value
511
+	}
512
+}
513
+
514
+func ValidateID(id string) error {
515
+	if id == "" {
516
+		return fmt.Errorf("Id can't be empty")
517
+	}
518
+	if strings.Contains(id, ":") {
519
+		return fmt.Errorf("Invalid character in id: ':'")
520
+	}
521
+	return nil
522
+}
523
+
496 524
 // Code c/c from io.Copy() modified to handle escape sequence
497 525
 func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
498 526
 	buf := make([]byte, 32*1024)