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)
This commit is contained in:
Solomon Hykes 2014-04-27 23:59:46 -07:00
Родитель 9dc66f8822
Коммит ff4ef50470
2 изменённых файлов: 134 добавлений и 1 удалений

128
graph/service.go Normal file
Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}