github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/reference/store.go (about) 1 package 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 "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 // force only works for tags 153 if digested, isDigest := ref.(reference.Canonical); isDigest { 154 return errors.WithStack(conflictingTagError("Cannot overwrite digest " + digested.Digest().String())) 155 } 156 157 if !force { 158 return errors.WithStack( 159 conflictingTagError( 160 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()), 161 ), 162 ) 163 } 164 165 if store.referencesByIDCache[oldID] != nil { 166 delete(store.referencesByIDCache[oldID], refStr) 167 if len(store.referencesByIDCache[oldID]) == 0 { 168 delete(store.referencesByIDCache, oldID) 169 } 170 } 171 } 172 173 repository[refStr] = id 174 if store.referencesByIDCache[id] == nil { 175 store.referencesByIDCache[id] = make(map[string]reference.Named) 176 } 177 store.referencesByIDCache[id][refStr] = ref 178 179 return store.save() 180 } 181 182 // Delete deletes a reference from the store. It returns true if a deletion 183 // happened, or false otherwise. 184 func (store *store) Delete(ref reference.Named) (bool, error) { 185 ref, err := favorDigest(ref) 186 if err != nil { 187 return false, err 188 } 189 190 ref = reference.TagNameOnly(ref) 191 192 refName := reference.FamiliarName(ref) 193 refStr := reference.FamiliarString(ref) 194 195 store.mu.Lock() 196 defer store.mu.Unlock() 197 198 repository, exists := store.Repositories[refName] 199 if !exists { 200 return false, ErrDoesNotExist 201 } 202 203 if id, exists := repository[refStr]; exists { 204 delete(repository, refStr) 205 if len(repository) == 0 { 206 delete(store.Repositories, refName) 207 } 208 if store.referencesByIDCache[id] != nil { 209 delete(store.referencesByIDCache[id], refStr) 210 if len(store.referencesByIDCache[id]) == 0 { 211 delete(store.referencesByIDCache, id) 212 } 213 } 214 return true, store.save() 215 } 216 217 return false, ErrDoesNotExist 218 } 219 220 // Get retrieves an item from the store by reference 221 func (store *store) Get(ref reference.Named) (digest.Digest, error) { 222 if canonical, ok := ref.(reference.Canonical); ok { 223 // If reference contains both tag and digest, only 224 // lookup by digest as it takes precedence over 225 // tag, until tag/digest combos are stored. 226 if _, ok := ref.(reference.Tagged); ok { 227 var err error 228 ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest()) 229 if err != nil { 230 return "", err 231 } 232 } 233 } else { 234 ref = reference.TagNameOnly(ref) 235 } 236 237 refName := reference.FamiliarName(ref) 238 refStr := reference.FamiliarString(ref) 239 240 store.mu.RLock() 241 defer store.mu.RUnlock() 242 243 repository, exists := store.Repositories[refName] 244 if !exists || repository == nil { 245 return "", ErrDoesNotExist 246 } 247 248 id, exists := repository[refStr] 249 if !exists { 250 return "", ErrDoesNotExist 251 } 252 253 return id, nil 254 } 255 256 // References returns a slice of references to the given ID. The slice 257 // will be nil if there are no references to this ID. 258 func (store *store) References(id digest.Digest) []reference.Named { 259 store.mu.RLock() 260 defer store.mu.RUnlock() 261 262 // Convert the internal map to an array for two reasons: 263 // 1) We must not return a mutable 264 // 2) It would be ugly to expose the extraneous map keys to callers. 265 266 var references []reference.Named 267 for _, ref := range store.referencesByIDCache[id] { 268 references = append(references, ref) 269 } 270 271 sort.Sort(lexicalRefs(references)) 272 273 return references 274 } 275 276 // ReferencesByName returns the references for a given repository name. 277 // If there are no references known for this repository name, 278 // ReferencesByName returns nil. 279 func (store *store) ReferencesByName(ref reference.Named) []Association { 280 refName := reference.FamiliarName(ref) 281 282 store.mu.RLock() 283 defer store.mu.RUnlock() 284 285 repository, exists := store.Repositories[refName] 286 if !exists { 287 return nil 288 } 289 290 var associations []Association 291 for refStr, refID := range repository { 292 ref, err := reference.ParseNormalizedNamed(refStr) 293 if err != nil { 294 // Should never happen 295 return nil 296 } 297 associations = append(associations, 298 Association{ 299 Ref: ref, 300 ID: refID, 301 }) 302 } 303 304 sort.Sort(lexicalAssociations(associations)) 305 306 return associations 307 } 308 309 func (store *store) save() error { 310 // Store the json 311 jsonData, err := json.Marshal(store) 312 if err != nil { 313 return err 314 } 315 return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600) 316 } 317 318 func (store *store) reload() error { 319 f, err := os.Open(store.jsonPath) 320 if err != nil { 321 return err 322 } 323 defer f.Close() 324 if err := json.NewDecoder(f).Decode(&store); err != nil { 325 return err 326 } 327 328 for _, repository := range store.Repositories { 329 for refStr, refID := range repository { 330 ref, err := reference.ParseNormalizedNamed(refStr) 331 if err != nil { 332 // Should never happen 333 continue 334 } 335 if store.referencesByIDCache[refID] == nil { 336 store.referencesByIDCache[refID] = make(map[string]reference.Named) 337 } 338 store.referencesByIDCache[refID][refStr] = ref 339 } 340 } 341 342 return nil 343 }