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