Browse code

Start separating the image subsystem

This is a first step towards moving all code related to local
manipulation of images into a cleanly separated subsystem,
accessible via a stable set of commands in the engine API.

`graph.TagStore` now implements `engine.Installer`. For now, it
is installed by `Server.InitServer`, along with all other Server
commands. However this will change in future patches.

`graph.TagStore.Install` registers the following commands:

* `image_set` creates a new image and stores it locally.
* `image_get` returns information about an image stored locally.
* `image_tag` assigns a new name and tag to an existing image.

These commands are a pre-requisite for moving 'push' and 'pull'
out of `Server`.

Docker-DCO-1.1-Signed-off-by: Solomon Hykes <solomon@docker.com> (github: shykes)

Solomon Hykes authored on 2014/04/28 15:59:46
Showing 2 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,128 @@
0
+package graph
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/dotcloud/docker/engine"
5
+	"github.com/dotcloud/docker/image"
6
+	"github.com/dotcloud/docker/utils"
7
+)
8
+
9
+func (s *TagStore) Install(eng *engine.Engine) error {
10
+	eng.Register("image_set", s.CmdSet)
11
+	eng.Register("image_tag", s.CmdTag)
12
+	eng.Register("image_get", s.CmdGet)
13
+	return nil
14
+}
15
+
16
+// CmdSet stores a new image in the graph.
17
+// Images are stored in the graph using 4 elements:
18
+//	- A user-defined ID
19
+//	- A collection of metadata describing the image
20
+//	- A directory tree stored as a tar archive (also called the "layer")
21
+//	- A reference to a "parent" ID on top of which the layer should be applied
22
+//
23
+// NOTE: even though the parent ID is only useful in relation to the layer and how
24
+// to apply it (ie you could represent the full directory tree as 'parent_layer + layer',
25
+// it is treated as a top-level property of the image. This is an artifact of early
26
+// design and should probably be cleaned up in the future to simplify the design.
27
+//
28
+// Syntax: image_set ID
29
+// Input:
30
+//	- Layer content must be streamed in tar format on stdin. An empty input is
31
+//	valid and represents a nil layer.
32
+//
33
+//	- Image metadata must be passed in the command environment.
34
+//		'json': a json-encoded object with all image metadata.
35
+//			It will be stored as-is, without any encoding/decoding artifacts.
36
+//			That is a requirement of the current registry client implementation,
37
+//			because a re-encoded json might invalidate the image checksum at
38
+//			the next upload, even with functionaly identical content.
39
+func (s *TagStore) CmdSet(job *engine.Job) engine.Status {
40
+	if len(job.Args) != 1 {
41
+		return job.Errorf("usage: %s NAME", job.Name)
42
+	}
43
+	var (
44
+		imgJSON = []byte(job.Getenv("json"))
45
+		layer   = job.Stdin
46
+	)
47
+	if len(imgJSON) == 0 {
48
+		return job.Errorf("mandatory key 'json' is not set")
49
+	}
50
+	// We have to pass an *image.Image object, even though it will be completely
51
+	// ignored in favor of the redundant json data.
52
+	// FIXME: the current prototype of Graph.Register is stupid and redundant.
53
+	img, err := image.NewImgJSON(imgJSON)
54
+	if err != nil {
55
+		return job.Error(err)
56
+	}
57
+	if err := s.graph.Register(imgJSON, layer, img); err != nil {
58
+		return job.Error(err)
59
+	}
60
+	return engine.StatusOK
61
+}
62
+
63
+// CmdTag assigns a new name and tag to an existing image. If the tag already exists,
64
+// it is changed and the image previously referenced by the tag loses that reference.
65
+// This may cause the old image to be garbage-collected if its reference count reaches zero.
66
+//
67
+// Syntax: image_tag NEWNAME OLDNAME
68
+// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0
69
+func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
70
+	if len(job.Args) != 2 {
71
+		return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name)
72
+	}
73
+	var (
74
+		newName = job.Args[0]
75
+		oldName = job.Args[1]
76
+	)
77
+	newRepo, newTag := utils.ParseRepositoryTag(newName)
78
+	// FIXME: Set should either parse both old and new name, or neither.
79
+	// 	the current prototype is inconsistent.
80
+	if err := s.Set(newRepo, newTag, oldName, true); err != nil {
81
+		return job.Error(err)
82
+	}
83
+	return engine.StatusOK
84
+}
85
+
86
+// CmdGet returns information about an image.
87
+// If the image doesn't exist, an empty object is returned, to allow
88
+// checking for an image's existence.
89
+func (s *TagStore) CmdGet(job *engine.Job) engine.Status {
90
+	if len(job.Args) != 1 {
91
+		return job.Errorf("usage: %s NAME", job.Name)
92
+	}
93
+	name := job.Args[0]
94
+	res := &engine.Env{}
95
+	img, err := s.LookupImage(name)
96
+	// Note: if the image doesn't exist, LookupImage returns
97
+	// nil, nil.
98
+	if err != nil {
99
+		return job.Error(err)
100
+	}
101
+	if img != nil {
102
+		// We don't directly expose all fields of the Image objects,
103
+		// to maintain a clean public API which we can maintain over
104
+		// time even if the underlying structure changes.
105
+		// We should have done this with the Image object to begin with...
106
+		// but we didn't, so now we're doing it here.
107
+		//
108
+		// Fields that we're probably better off not including:
109
+		//	- ID (the caller already knows it, and we stay more flexible on
110
+		//		naming down the road)
111
+		//	- Parent. That field is really an implementation detail of
112
+		//		layer storage ("layer is a diff against this other layer).
113
+		//		It doesn't belong at the same level as author/description/etc.
114
+		//	- Config/ContainerConfig. Those structs have the same sprawl problem,
115
+		//		so we shouldn't include them wholesale either.
116
+		//	- Comment: initially created to fulfill the "every image is a git commit"
117
+		//		metaphor, in practice people either ignore it or use it as a
118
+		//		generic description field which it isn't. On deprecation shortlist.
119
+		res.Set("created", fmt.Sprintf("%v", img.Created))
120
+		res.Set("author", img.Author)
121
+		res.Set("os", img.OS)
122
+		res.Set("architecture", img.Architecture)
123
+		res.Set("docker_version", img.DockerVersion)
124
+	}
125
+	res.WriteTo(job.Stdout)
126
+	return engine.StatusOK
127
+}
... ...
@@ -113,7 +113,7 @@ func InitServer(job *engine.Job) engine.Status {
113 113
 		"start":            srv.ContainerStart,
114 114
 		"kill":             srv.ContainerKill,
115 115
 		"wait":             srv.ContainerWait,
116
-		"tag":              srv.ImageTag,
116
+		"tag":              srv.ImageTag, // FIXME merge with "image_tag"
117 117
 		"resize":           srv.ContainerResize,
118 118
 		"commit":           srv.ContainerCommit,
119 119
 		"info":             srv.DockerInfo,
... ...
@@ -143,6 +143,11 @@ func InitServer(job *engine.Job) engine.Status {
143 143
 			return job.Error(err)
144 144
 		}
145 145
 	}
146
+	// Install image-related commands from the image subsystem.
147
+	// See `graph/service.go`
148
+	if err := srv.daemon.Repositories().Install(job.Eng); err != nil {
149
+		return job.Error(err)
150
+	}
146 151
 	return engine.StatusOK
147 152
 }
148 153