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