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