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 }