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