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  }