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)
| 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 |
|