Show shorthand container IDs for convenience. Shorthand IDs (or any non-conflicting prefix) can be used to lookup containers
This commit is contained in:
parent
3e3a53e607
commit
f13fe3e23b
|
@ -226,7 +226,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
if err := container.Stop(); err != nil {
|
if err := container.Stop(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(stdout, container.Id)
|
fmt.Fprintln(stdout, container.ShortId())
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("No such container: %s", name)
|
return fmt.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||||
if err := container.Restart(); err != nil {
|
if err := container.Restart(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(stdout, container.Id)
|
fmt.Fprintln(stdout, container.ShortId())
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("No such container: %s", name)
|
return fmt.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
if err := container.Start(); err != nil {
|
if err := container.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(stdout, container.Id)
|
fmt.Fprintln(stdout, container.ShortId())
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("No such container: %s", name)
|
return fmt.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
@ -659,7 +659,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
command = Trunc(command, 20)
|
command = Trunc(command, 20)
|
||||||
}
|
}
|
||||||
for idx, field := range []string{
|
for idx, field := range []string{
|
||||||
/* ID */ container.Id,
|
/* ID */ container.ShortId(),
|
||||||
/* IMAGE */ srv.runtime.repositories.ImageName(container.Image),
|
/* IMAGE */ srv.runtime.repositories.ImageName(container.Image),
|
||||||
/* COMMAND */ command,
|
/* COMMAND */ command,
|
||||||
/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago",
|
/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago",
|
||||||
|
@ -674,7 +674,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
}
|
}
|
||||||
w.Write([]byte{'\n'})
|
w.Write([]byte{'\n'})
|
||||||
} else {
|
} else {
|
||||||
stdout.Write([]byte(container.Id + "\n"))
|
stdout.Write([]byte(container.ShortId() + "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !*quiet {
|
if !*quiet {
|
||||||
|
@ -965,7 +965,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
if err := container.Start(); err != nil {
|
if err := container.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(stdout, container.Id)
|
fmt.Fprintln(stdout, container.ShortId())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -555,6 +555,18 @@ func (container *Container) Unmount() error {
|
||||||
return Unmount(container.RootfsPath())
|
return Unmount(container.RootfsPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShortId returns a shorthand version of the container's id for convenience.
|
||||||
|
// A collision with other container shorthands is very unlikely, but possible.
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
|
||||||
func (container *Container) logPath(name string) string {
|
func (container *Container) logPath(name string) string {
|
||||||
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
|
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ type Runtime struct {
|
||||||
graph *Graph
|
graph *Graph
|
||||||
repositories *TagStore
|
repositories *TagStore
|
||||||
authConfig *auth.AuthConfig
|
authConfig *auth.AuthConfig
|
||||||
|
idIndex *TruncIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
var sysInitPath string
|
var sysInitPath string
|
||||||
|
@ -47,7 +48,11 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runtime *Runtime) Get(id string) *Container {
|
func (runtime *Runtime) Get(name string) *Container {
|
||||||
|
id, err := runtime.idIndex.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
e := runtime.getContainerElement(id)
|
e := runtime.getContainerElement(id)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -72,6 +77,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
|
||||||
// Generate id
|
// Generate id
|
||||||
id := GenerateId()
|
id := GenerateId()
|
||||||
// Generate default hostname
|
// Generate default hostname
|
||||||
|
// FIXME: the lxc template no longer needs to set a default hostname
|
||||||
if config.Hostname == "" {
|
if config.Hostname == "" {
|
||||||
config.Hostname = id[:12]
|
config.Hostname = id[:12]
|
||||||
}
|
}
|
||||||
|
@ -142,6 +148,7 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||||
}
|
}
|
||||||
// done
|
// done
|
||||||
runtime.containers.PushBack(container)
|
runtime.containers.PushBack(container)
|
||||||
|
runtime.idIndex.Add(container.Id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +178,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Deregister the container before removing its directory, to avoid race conditions
|
// Deregister the container before removing its directory, to avoid race conditions
|
||||||
|
runtime.idIndex.Delete(container.Id)
|
||||||
runtime.containers.Remove(element)
|
runtime.containers.Remove(element)
|
||||||
if err := os.RemoveAll(container.root); err != nil {
|
if err := os.RemoveAll(container.root); err != nil {
|
||||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
|
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
|
||||||
|
@ -222,6 +230,7 @@ func (runtime *Runtime) restore() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: harmonize with NewGraph()
|
||||||
func NewRuntime() (*Runtime, error) {
|
func NewRuntime() (*Runtime, error) {
|
||||||
return NewRuntimeFromDirectory("/var/lib/docker")
|
return NewRuntimeFromDirectory("/var/lib/docker")
|
||||||
}
|
}
|
||||||
|
@ -259,6 +268,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||||
graph: g,
|
graph: g,
|
||||||
repositories: repositories,
|
repositories: repositories,
|
||||||
authConfig: authConfig,
|
authConfig: authConfig,
|
||||||
|
idIndex: NewTruncIndex(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runtime.restore(); err != nil {
|
if err := runtime.restore(); err != nil {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/dotcloud/docker/rcli"
|
"github.com/dotcloud/docker/rcli"
|
||||||
|
"index/suffixarray"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -270,3 +271,66 @@ func getTotalUsedFds() int {
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
||||||
|
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
||||||
|
type TruncIndex struct {
|
||||||
|
index *suffixarray.Index
|
||||||
|
ids map[string]bool
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTruncIndex() *TruncIndex {
|
||||||
|
return &TruncIndex{
|
||||||
|
index: suffixarray.New([]byte{' '}),
|
||||||
|
ids: make(map[string]bool),
|
||||||
|
bytes: []byte{' '},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *TruncIndex) Add(id string) error {
|
||||||
|
if strings.Contains(id, " ") {
|
||||||
|
return fmt.Errorf("Illegal character: ' '")
|
||||||
|
}
|
||||||
|
if _, exists := idx.ids[id]; exists {
|
||||||
|
return fmt.Errorf("Id already exists: %s", id)
|
||||||
|
}
|
||||||
|
idx.ids[id] = true
|
||||||
|
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
||||||
|
idx.index = suffixarray.New(idx.bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *TruncIndex) Delete(id string) error {
|
||||||
|
if _, exists := idx.ids[id]; !exists {
|
||||||
|
return fmt.Errorf("No such id: %s", id)
|
||||||
|
}
|
||||||
|
before, after, err := idx.lookup(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(idx.ids, id)
|
||||||
|
idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
|
||||||
|
idx.index = suffixarray.New(idx.bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *TruncIndex) lookup(s string) (int, int, error) {
|
||||||
|
offsets := idx.index.Lookup([]byte(" "+s), -1)
|
||||||
|
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
|
||||||
|
if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
|
||||||
|
return -1, -1, fmt.Errorf("No such id: %s", s)
|
||||||
|
}
|
||||||
|
offsetBefore := offsets[0] + 1
|
||||||
|
offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
|
||||||
|
return offsetBefore, offsetAfter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idx *TruncIndex) Get(s string) (string, error) {
|
||||||
|
before, after, err := idx.lookup(s)
|
||||||
|
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(idx.bytes[before:after]), err
|
||||||
|
}
|
||||||
|
|
|
@ -124,3 +124,85 @@ func TestWriteBroadcaster(t *testing.T) {
|
||||||
|
|
||||||
writer.Close()
|
writer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
|
||||||
|
func TestTruncIndex(t *testing.T) {
|
||||||
|
index := NewTruncIndex()
|
||||||
|
// Get on an empty index
|
||||||
|
if _, err := index.Get("foobar"); err == nil {
|
||||||
|
t.Fatal("Get on an empty index should return an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spaces should be illegal in an id
|
||||||
|
if err := index.Add("I have a space"); err == nil {
|
||||||
|
t.Fatalf("Adding an id with ' ' should return an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
|
||||||
|
// Add an id
|
||||||
|
if err := index.Add(id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Get a non-existing id
|
||||||
|
assertIndexGet(t, index, "abracadabra", "", true)
|
||||||
|
// Get the exact id
|
||||||
|
assertIndexGet(t, index, id, id, false)
|
||||||
|
// The first letter should match
|
||||||
|
assertIndexGet(t, index, id[:1], id, false)
|
||||||
|
// The first half should match
|
||||||
|
assertIndexGet(t, index, id[:len(id)/2], id, false)
|
||||||
|
// The second half should NOT match
|
||||||
|
assertIndexGet(t, index, id[len(id)/2:], "", true)
|
||||||
|
|
||||||
|
id2 := id[:6] + "blabla"
|
||||||
|
// Add an id
|
||||||
|
if err := index.Add(id2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Both exact IDs should work
|
||||||
|
assertIndexGet(t, index, id, id, false)
|
||||||
|
assertIndexGet(t, index, id2, id2, false)
|
||||||
|
|
||||||
|
// 6 characters or less should conflict
|
||||||
|
assertIndexGet(t, index, id[:6], "", true)
|
||||||
|
assertIndexGet(t, index, id[:4], "", true)
|
||||||
|
assertIndexGet(t, index, id[:1], "", true)
|
||||||
|
|
||||||
|
// 7 characters should NOT conflict
|
||||||
|
assertIndexGet(t, index, id[:7], id, false)
|
||||||
|
assertIndexGet(t, index, id2[:7], id2, false)
|
||||||
|
|
||||||
|
// Deleting a non-existing id should return an error
|
||||||
|
if err := index.Delete("non-existing"); err == nil {
|
||||||
|
t.Fatalf("Deleting a non-existing id should return an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting id2 should remove conflicts
|
||||||
|
if err := index.Delete(id2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// id2 should no longer work
|
||||||
|
assertIndexGet(t, index, id2, "", true)
|
||||||
|
assertIndexGet(t, index, id2[:7], "", true)
|
||||||
|
assertIndexGet(t, index, id2[:11], "", true)
|
||||||
|
|
||||||
|
// conflicts between id and id2 should be gone
|
||||||
|
assertIndexGet(t, index, id[:6], id, false)
|
||||||
|
assertIndexGet(t, index, id[:4], id, false)
|
||||||
|
assertIndexGet(t, index, id[:1], id, false)
|
||||||
|
|
||||||
|
// non-conflicting substrings should still not conflict
|
||||||
|
assertIndexGet(t, index, id[:7], id, false)
|
||||||
|
assertIndexGet(t, index, id[:15], id, false)
|
||||||
|
assertIndexGet(t, index, id, id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
|
||||||
|
if result, err := index.Get(input); err != nil && !expectError {
|
||||||
|
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
||||||
|
} else if err == nil && expectError {
|
||||||
|
t.Fatalf("Getting '%s' should return an error", input)
|
||||||
|
} else if result != expectedResult {
|
||||||
|
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue