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