github.com/vvnotw/moby@v1.13.1/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 // ErrIllegalChar is returned when a space is in the ID 20 ErrIllegalChar = errors.New("illegal character: ' '") 21 22 // ErrNotExist is returned when ID or its prefix not found in index. 23 ErrNotExist = errors.New("ID does not exist") 24 ) 25 26 // ErrAmbiguousPrefix is returned if the prefix was ambiguous 27 // (multiple ids for the prefix). 28 type ErrAmbiguousPrefix struct { 29 prefix string 30 } 31 32 func (e ErrAmbiguousPrefix) Error() string { 33 return fmt.Sprintf("Multiple IDs found with provided prefix: %s", e.prefix) 34 } 35 36 // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. 37 // This is used to retrieve image and container IDs by more convenient shorthand prefixes. 38 type TruncIndex struct { 39 sync.RWMutex 40 trie *patricia.Trie 41 ids map[string]struct{} 42 } 43 44 // NewTruncIndex creates a new TruncIndex and initializes with a list of IDs. 45 func NewTruncIndex(ids []string) (idx *TruncIndex) { 46 idx = &TruncIndex{ 47 ids: make(map[string]struct{}), 48 49 // Change patricia max prefix per node length, 50 // because our len(ID) always 64 51 trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)), 52 } 53 for _, id := range ids { 54 idx.addID(id) 55 } 56 return 57 } 58 59 func (idx *TruncIndex) addID(id string) error { 60 if strings.Contains(id, " ") { 61 return ErrIllegalChar 62 } 63 if id == "" { 64 return ErrEmptyPrefix 65 } 66 if _, exists := idx.ids[id]; exists { 67 return fmt.Errorf("id already exists: '%s'", id) 68 } 69 idx.ids[id] = struct{}{} 70 if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted { 71 return fmt.Errorf("failed to insert id: %s", id) 72 } 73 return nil 74 } 75 76 // Add adds a new ID to the TruncIndex. 77 func (idx *TruncIndex) Add(id string) error { 78 idx.Lock() 79 defer idx.Unlock() 80 if err := idx.addID(id); err != nil { 81 return err 82 } 83 return nil 84 } 85 86 // Delete removes an ID from the TruncIndex. If there are multiple IDs 87 // with the given prefix, an error is thrown. 88 func (idx *TruncIndex) Delete(id string) error { 89 idx.Lock() 90 defer idx.Unlock() 91 if _, exists := idx.ids[id]; !exists || id == "" { 92 return fmt.Errorf("no such id: '%s'", id) 93 } 94 delete(idx.ids, id) 95 if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted { 96 return fmt.Errorf("no such id: '%s'", id) 97 } 98 return nil 99 } 100 101 // Get retrieves an ID from the TruncIndex. If there are multiple IDs 102 // with the given prefix, an error is thrown. 103 func (idx *TruncIndex) Get(s string) (string, error) { 104 if s == "" { 105 return "", ErrEmptyPrefix 106 } 107 var ( 108 id string 109 ) 110 subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error { 111 if id != "" { 112 // we haven't found the ID if there are two or more IDs 113 id = "" 114 return ErrAmbiguousPrefix{prefix: string(prefix)} 115 } 116 id = string(prefix) 117 return nil 118 } 119 120 idx.RLock() 121 defer idx.RUnlock() 122 if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil { 123 return "", err 124 } 125 if id != "" { 126 return id, nil 127 } 128 return "", ErrNotExist 129 } 130 131 // Iterate iterates over all stored IDs, and passes each of them to the given handler. 132 func (idx *TruncIndex) Iterate(handler func(id string)) { 133 idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { 134 handler(string(prefix)) 135 return nil 136 }) 137 }