diff --git a/commands.go b/commands.go index 4ccdba67d6..105f76be5e 100644 --- a/commands.go +++ b/commands.go @@ -372,7 +372,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str fmt.Fprintf(w, "ID\tCREATED\tCREATED BY\n") return image.WalkHistory(func(img *Image) error { fmt.Fprintf(w, "%s\t%s\t%s\n", - srv.runtime.repositories.ImageName(img.Id), + srv.runtime.repositories.ImageName(img.ShortId()), HumanDuration(time.Now().Sub(img.Created))+" ago", strings.Join(img.ContainerConfig.Cmd, " "), ) @@ -458,7 +458,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri return err } } - fmt.Fprintln(stdout, img.Id) + fmt.Fprintln(stdout, img.ShortId()) return nil } @@ -591,7 +591,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri for idx, field := range []string{ /* REPOSITORY */ name, /* TAG */ tag, - /* ID */ id, + /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { @@ -603,7 +603,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w.Write([]byte{'\n'}) } else { - stdout.Write([]byte(image.Id + "\n")) + stdout.Write([]byte(image.ShortId() + "\n")) } } } @@ -614,7 +614,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri for idx, field := range []string{ /* REPOSITORY */ "", /* TAG */ "", - /* ID */ id, + /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { @@ -626,7 +626,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w.Write([]byte{'\n'}) } else { - stdout.Write([]byte(image.Id + "\n")) + stdout.Write([]byte(image.ShortId() + "\n")) } } } @@ -700,7 +700,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri if err != nil { return err } - fmt.Fprintln(stdout, img.Id) + fmt.Fprintln(stdout, img.ShortId()) return nil } diff --git a/container.go b/container.go index 502109ef96..a7094bf5ec 100644 --- a/container.go +++ b/container.go @@ -566,11 +566,7 @@ func (container *Container) Unmount() error { // In case of a collision a lookup with Runtime.Get() will fail, and the caller // will need to use a langer prefix, or the full-length container Id. func (container *Container) ShortId() string { - shortLen := 12 - if len(container.Id) < shortLen { - shortLen = len(container.Id) - } - return container.Id[:shortLen] + return TruncateId(container.Id) } func (container *Container) logPath(name string) string { diff --git a/graph.go b/graph.go index 09bc1d5bed..ca126dc0e5 100644 --- a/graph.go +++ b/graph.go @@ -12,7 +12,8 @@ import ( // A Graph is a store for versioned filesystem images, and the relationship between them. type Graph struct { - Root string + Root string + idIndex *TruncIndex } // NewGraph instanciates a new graph at the given root path in the filesystem. @@ -26,9 +27,26 @@ func NewGraph(root string) (*Graph, error) { if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) { return nil, err } - return &Graph{ - Root: abspath, - }, nil + graph := &Graph{ + Root: abspath, + idIndex: NewTruncIndex(), + } + if err := graph.restore(); err != nil { + return nil, err + } + return graph, nil +} + +func (graph *Graph) restore() error { + dir, err := ioutil.ReadDir(graph.Root) + if err != nil { + return err + } + for _, v := range dir { + id := v.Name() + graph.idIndex.Add(id) + } + return nil } // FIXME: Implement error subclass instead of looking at the error text @@ -47,7 +65,11 @@ func (graph *Graph) Exists(id string) bool { } // Get returns the image with the given id, or an error if the image doesn't exist. -func (graph *Graph) Get(id string) (*Image, error) { +func (graph *Graph) Get(name string) (*Image, error) { + id, err := graph.idIndex.Get(name) + if err != nil { + return nil, err + } // FIXME: return nil when the image doesn't exist, instead of an error img, err := LoadImage(graph.imageRoot(id)) if err != nil { @@ -101,6 +123,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { return err } img.graph = graph + graph.idIndex.Add(img.Id) return nil } @@ -143,8 +166,11 @@ func (graph *Graph) Delete(id string) error { if err != nil { return err } + graph.idIndex.Delete(id) err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id)) if err != nil { + // FIXME: this introduces a race condition in Delete() if the image is already present + // in garbage. Let's store at random names in grabage instead. if isNotEmpty(err) { Debugf("The image %s is already present in garbage. Removing it.", id) if err = os.RemoveAll(garbage.imageRoot(id)); err != nil { @@ -170,7 +196,11 @@ func (graph *Graph) Undelete(id string) error { if err != nil { return err } - return os.Rename(garbage.imageRoot(id), graph.imageRoot(id)) + if err := os.Rename(garbage.imageRoot(id), graph.imageRoot(id)); err != nil { + return err + } + graph.idIndex.Add(id) + return nil } // GarbageCollect definitely deletes all images moved to the garbage diff --git a/image.go b/image.go index 1cd475f19b..19e5387f97 100644 --- a/image.go +++ b/image.go @@ -150,6 +150,10 @@ func (image *Image) Changes(rw string) ([]Change, error) { return Changes(layers, rw) } +func (image *Image) ShortId() string { + return TruncateId(image.Id) +} + func ValidateId(id string) error { if id == "" { return fmt.Errorf("Image id can't be empty") diff --git a/tags.go b/tags.go index 4f2b92e0bb..1b9cd19c83 100644 --- a/tags.go +++ b/tags.go @@ -106,7 +106,7 @@ func (store *TagStore) ImageName(id string) string { if names, exists := store.ById()[id]; exists && len(names) > 0 { return names[0] } - return id + return TruncateId(id) } func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { diff --git a/utils.go b/utils.go index a87544ff3c..381af1fe38 100644 --- a/utils.go +++ b/utils.go @@ -334,3 +334,15 @@ func (idx *TruncIndex) Get(s string) (string, error) { } return string(idx.bytes[before:after]), err } + +// TruncateId returns a shorthand version of a string identifier for convenience. +// A collision with other shorthands is very unlikely, but possible. +// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller +// will need to use a langer prefix, or the full-length Id. +func TruncateId(id string) string { + shortLen := 12 + if len(id) < shortLen { + shortLen = len(id) + } + return id[:shortLen] +}