From ff4ef504708bfaa51d4d361455689a21a031cc35 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Apr 2014 23:59:46 -0700 Subject: [PATCH] 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 (github: shykes) --- graph/service.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++ server/server.go | 7 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 graph/service.go diff --git a/graph/service.go b/graph/service.go new file mode 100644 index 0000000000..211babd0bd --- /dev/null +++ b/graph/service.go @@ -0,0 +1,128 @@ +package graph + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/utils" +) + +func (s *TagStore) Install(eng *engine.Engine) error { + eng.Register("image_set", s.CmdSet) + eng.Register("image_tag", s.CmdTag) + eng.Register("image_get", s.CmdGet) + return nil +} + +// CmdSet stores a new image in the graph. +// Images are stored in the graph using 4 elements: +// - A user-defined ID +// - A collection of metadata describing the image +// - A directory tree stored as a tar archive (also called the "layer") +// - A reference to a "parent" ID on top of which the layer should be applied +// +// NOTE: even though the parent ID is only useful in relation to the layer and how +// to apply it (ie you could represent the full directory tree as 'parent_layer + layer', +// it is treated as a top-level property of the image. This is an artifact of early +// design and should probably be cleaned up in the future to simplify the design. +// +// Syntax: image_set ID +// Input: +// - Layer content must be streamed in tar format on stdin. An empty input is +// valid and represents a nil layer. +// +// - Image metadata must be passed in the command environment. +// 'json': a json-encoded object with all image metadata. +// It will be stored as-is, without any encoding/decoding artifacts. +// That is a requirement of the current registry client implementation, +// because a re-encoded json might invalidate the image checksum at +// the next upload, even with functionaly identical content. +func (s *TagStore) CmdSet(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + var ( + imgJSON = []byte(job.Getenv("json")) + layer = job.Stdin + ) + if len(imgJSON) == 0 { + return job.Errorf("mandatory key 'json' is not set") + } + // We have to pass an *image.Image object, even though it will be completely + // ignored in favor of the redundant json data. + // FIXME: the current prototype of Graph.Register is stupid and redundant. + img, err := image.NewImgJSON(imgJSON) + if err != nil { + return job.Error(err) + } + if err := s.graph.Register(imgJSON, layer, img); err != nil { + return job.Error(err) + } + return engine.StatusOK +} + +// CmdTag assigns a new name and tag to an existing image. If the tag already exists, +// it is changed and the image previously referenced by the tag loses that reference. +// This may cause the old image to be garbage-collected if its reference count reaches zero. +// +// Syntax: image_tag NEWNAME OLDNAME +// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0 +func (s *TagStore) CmdTag(job *engine.Job) engine.Status { + if len(job.Args) != 2 { + return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name) + } + var ( + newName = job.Args[0] + oldName = job.Args[1] + ) + newRepo, newTag := utils.ParseRepositoryTag(newName) + // FIXME: Set should either parse both old and new name, or neither. + // the current prototype is inconsistent. + if err := s.Set(newRepo, newTag, oldName, true); err != nil { + return job.Error(err) + } + return engine.StatusOK +} + +// CmdGet returns information about an image. +// If the image doesn't exist, an empty object is returned, to allow +// checking for an image's existence. +func (s *TagStore) CmdGet(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + name := job.Args[0] + res := &engine.Env{} + img, err := s.LookupImage(name) + // Note: if the image doesn't exist, LookupImage returns + // nil, nil. + if err != nil { + return job.Error(err) + } + if img != nil { + // We don't directly expose all fields of the Image objects, + // to maintain a clean public API which we can maintain over + // time even if the underlying structure changes. + // We should have done this with the Image object to begin with... + // but we didn't, so now we're doing it here. + // + // Fields that we're probably better off not including: + // - ID (the caller already knows it, and we stay more flexible on + // naming down the road) + // - Parent. That field is really an implementation detail of + // layer storage ("layer is a diff against this other layer). + // It doesn't belong at the same level as author/description/etc. + // - Config/ContainerConfig. Those structs have the same sprawl problem, + // so we shouldn't include them wholesale either. + // - Comment: initially created to fulfill the "every image is a git commit" + // metaphor, in practice people either ignore it or use it as a + // generic description field which it isn't. On deprecation shortlist. + res.Set("created", fmt.Sprintf("%v", img.Created)) + res.Set("author", img.Author) + res.Set("os", img.OS) + res.Set("architecture", img.Architecture) + res.Set("docker_version", img.DockerVersion) + } + res.WriteTo(job.Stdout) + return engine.StatusOK +} diff --git a/server/server.go b/server/server.go index 30e5ef1f6f..ac2bb0cada 100644 --- a/server/server.go +++ b/server/server.go @@ -113,7 +113,7 @@ func InitServer(job *engine.Job) engine.Status { "start": srv.ContainerStart, "kill": srv.ContainerKill, "wait": srv.ContainerWait, - "tag": srv.ImageTag, + "tag": srv.ImageTag, // FIXME merge with "image_tag" "resize": srv.ContainerResize, "commit": srv.ContainerCommit, "info": srv.DockerInfo, @@ -143,6 +143,11 @@ func InitServer(job *engine.Job) engine.Status { return job.Error(err) } } + // Install image-related commands from the image subsystem. + // See `graph/service.go` + if err := srv.daemon.Repositories().Install(job.Eng); err != nil { + return job.Error(err) + } return engine.StatusOK }