github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/reference/store.go (about)

     1  package reference // import "github.com/Prakhar-Agarwal-byte/moby/reference"
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/distribution/reference"
    12  	"github.com/Prakhar-Agarwal-byte/moby/pkg/ioutils"
    13  	"github.com/opencontainers/go-digest"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // ErrDoesNotExist is returned if a reference is not found in the
    18  // store.
    19  var ErrDoesNotExist notFoundError = "reference does not exist"
    20  
    21  // An Association is a tuple associating a reference with an image ID.
    22  type Association struct {
    23  	Ref reference.Named
    24  	ID  digest.Digest
    25  }
    26  
    27  // Store provides the set of methods which can operate on a reference store.
    28  type Store interface {
    29  	References(id digest.Digest) []reference.Named
    30  	ReferencesByName(ref reference.Named) []Association
    31  	AddTag(ref reference.Named, id digest.Digest, force bool) error
    32  	AddDigest(ref reference.Canonical, id digest.Digest, force bool) error
    33  	Delete(ref reference.Named) (bool, error)
    34  	Get(ref reference.Named) (digest.Digest, error)
    35  }
    36  
    37  type refStore struct {
    38  	mu sync.RWMutex
    39  	// jsonPath is the path to the file where the serialized tag data is
    40  	// stored.
    41  	jsonPath string
    42  	// Repositories is a map of repositories, indexed by name.
    43  	Repositories map[string]repository
    44  	// referencesByIDCache is a cache of references indexed by ID, to speed
    45  	// up References.
    46  	referencesByIDCache map[digest.Digest]map[string]reference.Named
    47  }
    48  
    49  // Repository maps tags to digests. The key is a stringified Reference,
    50  // including the repository name.
    51  type repository map[string]digest.Digest
    52  
    53  type lexicalRefs []reference.Named
    54  
    55  func (a lexicalRefs) Len() int      { return len(a) }
    56  func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    57  func (a lexicalRefs) Less(i, j int) bool {
    58  	return a[i].String() < a[j].String()
    59  }
    60  
    61  type lexicalAssociations []Association
    62  
    63  func (a lexicalAssociations) Len() int      { return len(a) }
    64  func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    65  func (a lexicalAssociations) Less(i, j int) bool {
    66  	return a[i].Ref.String() < a[j].Ref.String()
    67  }
    68  
    69  // NewReferenceStore creates a new reference store, tied to a file path where
    70  // the set of references are serialized in JSON format.
    71  func NewReferenceStore(jsonPath string) (Store, error) {
    72  	abspath, err := filepath.Abs(jsonPath)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	store := &refStore{
    78  		jsonPath:            abspath,
    79  		Repositories:        make(map[string]repository),
    80  		referencesByIDCache: make(map[digest.Digest]map[string]reference.Named),
    81  	}
    82  	// Load the json file if it exists, otherwise create it.
    83  	if err := store.reload(); os.IsNotExist(err) {
    84  		if err := store.save(); err != nil {
    85  			return nil, err
    86  		}
    87  	} else if err != nil {
    88  		return nil, err
    89  	}
    90  	return store, nil
    91  }
    92  
    93  // AddTag adds a tag reference to the store. If force is set to true, existing
    94  // references can be overwritten. This only works for tags, not digests.
    95  func (store *refStore) AddTag(ref reference.Named, id digest.Digest, force bool) error {
    96  	if _, isCanonical := ref.(reference.Canonical); isCanonical {
    97  		return errors.WithStack(invalidTagError("refusing to create a tag with a digest reference"))
    98  	}
    99  	return store.addReference(reference.TagNameOnly(ref), id, force)
   100  }
   101  
   102  // AddDigest adds a digest reference to the store.
   103  func (store *refStore) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
   104  	return store.addReference(ref, id, force)
   105  }
   106  
   107  func favorDigest(originalRef reference.Named) (reference.Named, error) {
   108  	ref := originalRef
   109  	// If the reference includes a digest and a tag, we must store only the
   110  	// digest.
   111  	canonical, isCanonical := originalRef.(reference.Canonical)
   112  	_, isNamedTagged := originalRef.(reference.NamedTagged)
   113  
   114  	if isCanonical && isNamedTagged {
   115  		trimmed, err := reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
   116  		if err != nil {
   117  			// should never happen
   118  			return originalRef, err
   119  		}
   120  		ref = trimmed
   121  	}
   122  	return ref, nil
   123  }
   124  
   125  func (store *refStore) addReference(ref reference.Named, id digest.Digest, force bool) error {
   126  	ref, err := favorDigest(ref)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	refName := reference.FamiliarName(ref)
   132  	refStr := reference.FamiliarString(ref)
   133  
   134  	if refName == string(digest.Canonical) {
   135  		return errors.WithStack(invalidTagError("refusing to create an ambiguous tag using digest algorithm as name"))
   136  	}
   137  
   138  	store.mu.Lock()
   139  	defer store.mu.Unlock()
   140  
   141  	repo, exists := store.Repositories[refName]
   142  	if !exists || repo == nil {
   143  		repo = make(map[string]digest.Digest)
   144  		store.Repositories[refName] = repo
   145  	}
   146  
   147  	oldID, exists := repo[refStr]
   148  
   149  	if exists {
   150  		if oldID == id {
   151  			// Nothing to do. The caller may have checked for this using store.Get in advance, but store.mu was unlocked in the meantime, so this can legitimately happen nevertheless.
   152  			return nil
   153  		}
   154  
   155  		// force only works for tags
   156  		if digested, isDigest := ref.(reference.Canonical); isDigest {
   157  			return errors.WithStack(conflictingTagError("cannot overwrite digest " + digested.Digest().String()))
   158  		}
   159  
   160  		if !force {
   161  			return errors.WithStack(
   162  				conflictingTagError(
   163  					fmt.Sprintf("tag %s is already set to image %s, use the force option to replace it", refStr, oldID.String()),
   164  				),
   165  			)
   166  		}
   167  
   168  		if store.referencesByIDCache[oldID] != nil {
   169  			delete(store.referencesByIDCache[oldID], refStr)
   170  			if len(store.referencesByIDCache[oldID]) == 0 {
   171  				delete(store.referencesByIDCache, oldID)
   172  			}
   173  		}
   174  	}
   175  
   176  	repo[refStr] = id
   177  	if store.referencesByIDCache[id] == nil {
   178  		store.referencesByIDCache[id] = make(map[string]reference.Named)
   179  	}
   180  	store.referencesByIDCache[id][refStr] = ref
   181  
   182  	return store.save()
   183  }
   184  
   185  // Delete deletes a reference from the store. It returns true if a deletion
   186  // happened, or false otherwise.
   187  func (store *refStore) Delete(ref reference.Named) (bool, error) {
   188  	ref, err := favorDigest(ref)
   189  	if err != nil {
   190  		return false, err
   191  	}
   192  
   193  	ref = reference.TagNameOnly(ref)
   194  
   195  	refName := reference.FamiliarName(ref)
   196  	refStr := reference.FamiliarString(ref)
   197  
   198  	store.mu.Lock()
   199  	defer store.mu.Unlock()
   200  
   201  	repo, exists := store.Repositories[refName]
   202  	if !exists {
   203  		return false, ErrDoesNotExist
   204  	}
   205  
   206  	if id, exists := repo[refStr]; exists {
   207  		delete(repo, refStr)
   208  		if len(repo) == 0 {
   209  			delete(store.Repositories, refName)
   210  		}
   211  		if store.referencesByIDCache[id] != nil {
   212  			delete(store.referencesByIDCache[id], refStr)
   213  			if len(store.referencesByIDCache[id]) == 0 {
   214  				delete(store.referencesByIDCache, id)
   215  			}
   216  		}
   217  		return true, store.save()
   218  	}
   219  
   220  	return false, ErrDoesNotExist
   221  }
   222  
   223  // Get retrieves an item from the store by reference
   224  func (store *refStore) Get(ref reference.Named) (digest.Digest, error) {
   225  	if canonical, ok := ref.(reference.Canonical); ok {
   226  		// If reference contains both tag and digest, only
   227  		// lookup by digest as it takes precedence over
   228  		// tag, until tag/digest combos are stored.
   229  		if _, ok := ref.(reference.Tagged); ok {
   230  			var err error
   231  			ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
   232  			if err != nil {
   233  				return "", err
   234  			}
   235  		}
   236  	} else {
   237  		ref = reference.TagNameOnly(ref)
   238  	}
   239  
   240  	refName := reference.FamiliarName(ref)
   241  	refStr := reference.FamiliarString(ref)
   242  
   243  	store.mu.RLock()
   244  	defer store.mu.RUnlock()
   245  
   246  	repo, exists := store.Repositories[refName]
   247  	if !exists || repo == nil {
   248  		return "", ErrDoesNotExist
   249  	}
   250  
   251  	id, exists := repo[refStr]
   252  	if !exists {
   253  		return "", ErrDoesNotExist
   254  	}
   255  
   256  	return id, nil
   257  }
   258  
   259  // References returns a slice of references to the given ID. The slice
   260  // will be nil if there are no references to this ID.
   261  func (store *refStore) References(id digest.Digest) []reference.Named {
   262  	store.mu.RLock()
   263  	defer store.mu.RUnlock()
   264  
   265  	// Convert the internal map to an array for two reasons:
   266  	// 1) We must not return a mutable
   267  	// 2) It would be ugly to expose the extraneous map keys to callers.
   268  
   269  	var references []reference.Named
   270  	for _, ref := range store.referencesByIDCache[id] {
   271  		references = append(references, ref)
   272  	}
   273  
   274  	sort.Sort(lexicalRefs(references))
   275  
   276  	return references
   277  }
   278  
   279  // ReferencesByName returns the references for a given repository name.
   280  // If there are no references known for this repository name,
   281  // ReferencesByName returns nil.
   282  func (store *refStore) ReferencesByName(ref reference.Named) []Association {
   283  	refName := reference.FamiliarName(ref)
   284  
   285  	store.mu.RLock()
   286  	defer store.mu.RUnlock()
   287  
   288  	repo, exists := store.Repositories[refName]
   289  	if !exists {
   290  		return nil
   291  	}
   292  
   293  	var associations []Association
   294  	for refStr, refID := range repo {
   295  		ref, err := reference.ParseNormalizedNamed(refStr)
   296  		if err != nil {
   297  			// Should never happen
   298  			return nil
   299  		}
   300  		associations = append(associations,
   301  			Association{
   302  				Ref: ref,
   303  				ID:  refID,
   304  			})
   305  	}
   306  
   307  	sort.Sort(lexicalAssociations(associations))
   308  
   309  	return associations
   310  }
   311  
   312  func (store *refStore) save() error {
   313  	// Store the json
   314  	jsonData, err := json.Marshal(store)
   315  	if err != nil {
   316  		return err
   317  	}
   318  	return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0o600)
   319  }
   320  
   321  func (store *refStore) reload() error {
   322  	f, err := os.Open(store.jsonPath)
   323  	if err != nil {
   324  		return err
   325  	}
   326  	defer f.Close()
   327  	if err := json.NewDecoder(f).Decode(&store); err != nil {
   328  		return err
   329  	}
   330  
   331  	for _, repo := range store.Repositories {
   332  		for refStr, refID := range repo {
   333  			ref, err := reference.ParseNormalizedNamed(refStr)
   334  			if err != nil {
   335  				// Should never happen
   336  				continue
   337  			}
   338  			if store.referencesByIDCache[refID] == nil {
   339  				store.referencesByIDCache[refID] = make(map[string]reference.Named)
   340  			}
   341  			store.referencesByIDCache[refID][refStr] = ref
   342  		}
   343  	}
   344  
   345  	return nil
   346  }