github.com/damirazo/docker@v1.9.0/pkg/truncindex/truncindex.go (about) 1 // Package truncindex provides a general 'index tree', used by Docker 2 // in order to be able to reference containers by only a few unambiguous 3 // characters of their id. 4 package truncindex 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 "sync" 11 12 "github.com/tchap/go-patricia/patricia" 13 ) 14 15 var ( 16 // ErrEmptyPrefix is an error returned if the prefix was empty. 17 ErrEmptyPrefix = errors.New("Prefix can't be empty") 18 19 // ErrAmbiguousPrefix is returned if the prefix was ambiguous 20 // (multiple ids for the prefix). 21 ErrAmbiguousPrefix = errors.New("Multiple IDs found with provided prefix") 22 23 // ErrIllegalChar is returned when a space is in the ID 24 ErrIllegalChar = errors.New("illegal character: ' '") 25 ) 26 27 // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. 28 // This is used to retrieve image and container IDs by more convenient shorthand prefixes. 29 type TruncIndex struct { 30 sync.RWMutex 31 trie *patricia.Trie 32 ids map[string]struct{} 33 } 34 35 // NewTruncIndex creates a new TruncIndex and initializes with a list of IDs. 36 func NewTruncIndex(ids []string) (idx *TruncIndex) { 37 idx = &TruncIndex{ 38 ids: make(map[string]struct{}), 39 40 // Change patricia max prefix per node length, 41 // because our len(ID) always 64 42 trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)), 43 } 44 for _, id := range ids { 45 idx.addID(id) 46 } 47 return 48 } 49 50 func (idx *TruncIndex) addID(id string) error { 51 if strings.Contains(id, " ") { 52 return ErrIllegalChar 53 } 54 if id == "" { 55 return ErrEmptyPrefix 56 } 57 if _, exists := idx.ids[id]; exists { 58 return fmt.Errorf("id already exists: '%s'", id) 59 } 60 idx.ids[id] = struct{}{} 61 if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted { 62 return fmt.Errorf("failed to insert id: %s", id) 63 } 64 return nil 65 } 66 67 // Add adds a new ID to the TruncIndex. 68 func (idx *TruncIndex) Add(id string) error { 69 idx.Lock() 70 defer idx.Unlock() 71 if err := idx.addID(id); err != nil { 72 return err 73 } 74 return nil 75 } 76 77 // Delete removes an ID from the TruncIndex. If there are multiple IDs 78 // with the given prefix, an error is thrown. 79 func (idx *TruncIndex) Delete(id string) error { 80 idx.Lock() 81 defer idx.Unlock() 82 if _, exists := idx.ids[id]; !exists || id == "" { 83 return fmt.Errorf("no such id: '%s'", id) 84 } 85 delete(idx.ids, id) 86 if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted { 87 return fmt.Errorf("no such id: '%s'", id) 88 } 89 return nil 90 } 91 92 // Get retrieves an ID from the TruncIndex. If there are multiple IDs 93 // with the given prefix, an error is thrown. 94 func (idx *TruncIndex) Get(s string) (string, error) { 95 if s == "" { 96 return "", ErrEmptyPrefix 97 } 98 var ( 99 id string 100 ) 101 subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error { 102 if id != "" { 103 // we haven't found the ID if there are two or more IDs 104 id = "" 105 return ErrAmbiguousPrefix 106 } 107 id = string(prefix) 108 return nil 109 } 110 111 idx.RLock() 112 defer idx.RUnlock() 113 if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil { 114 return "", err 115 } 116 if id != "" { 117 return id, nil 118 } 119 return "", fmt.Errorf("no such id: %s", s) 120 } 121 122 // Iterate iterates over all stored IDs, and passes each of them to the given handler. 123 func (idx *TruncIndex) Iterate(handler func(id string)) { 124 idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { 125 handler(string(prefix)) 126 return nil 127 }) 128 }