1// Copyright 2016 VMware, Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package cache 16 17import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "sort" 22 "sync" 23 24 "github.com/vmware/vic/lib/apiservers/engine/backends/kv" 25 "github.com/vmware/vic/lib/apiservers/portlayer/client" 26 27 "github.com/docker/distribution/digest" 28 "github.com/docker/docker/reference" 29 30 log "github.com/sirupsen/logrus" 31) 32 33// repoCache is a cache of the docker repository information. 34// This info will help to provide proper tag and digest support 35// 36// The cache will be persisted to disk via the portlayer k/v 37// store and will be restored at system start 38// 39// This code is a heavy leverage of docker's reference store: 40// github.com/docker/docker/reference/store.go 41 42var ( 43 rCache *repoCache 44 repoKey = "repositories" 45) 46 47// Repo provides the set of methods which can operate on a tag store. 48type Repo interface { 49 References(imageID string) []reference.Named 50 ReferencesByName(ref reference.Named) []Association 51 Delete(ref reference.Named, save bool) (bool, error) 52 Get(ref reference.Named) (string, error) 53 54 Save() error 55 GetImageID(layerID string) string 56 Tags(imageID string) []string 57 Digests(imageID string) []string 58 AddReference(ref reference.Named, imageID string, force bool, layerID string, save bool) error 59 60 // Remove will remove from the cache and returns the 61 // stringified Named if successful -- save bool instructs 62 // func to persist to portlayer k/v or not 63 Remove(ref string, save bool) (string, error) 64} 65 66type repoCache struct { 67 // client is needed for k/v store operations 68 client *client.PortLayer 69 70 mu sync.RWMutex 71 // repositories is a map of repositories, indexed by name. 72 Repositories map[string]repository 73 // referencesByIDCache is a cache of references indexed by imageID 74 referencesByIDCache map[string]map[string]reference.Named 75 // Layers is a map of layerIDs to imageIDs 76 // TODO: we might be able to remove this later -- currently 77 // needed because an ImageID isn't generated for every pull 78 Layers map[string]string 79 // images is a map of imageIDs to layerIDs 80 // TODO: much like the Layers map this might be able to be 81 // removed 82 images map[string]string 83} 84 85// Repository maps tags to image IDs. The key is a a stringified Reference, 86// including the repository name. 87type repository map[string]string 88 89var ( 90 // ErrDoesNotExist returned if a reference is not found in the 91 // store. 92 ErrDoesNotExist = errors.New("reference does not exist") 93) 94 95// An Association is a tuple associating a reference with an image ID. 96type Association struct { 97 Ref reference.Named 98 ImageID string 99} 100 101type lexicalRefs []reference.Named 102 103func (a lexicalRefs) Len() int { return len(a) } 104func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 105func (a lexicalRefs) Less(i, j int) bool { return a[i].String() < a[j].String() } 106 107type lexicalAssociations []Association 108 109func (a lexicalAssociations) Len() int { return len(a) } 110func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 111func (a lexicalAssociations) Less(i, j int) bool { return a[i].Ref.String() < a[j].Ref.String() } 112 113// RepositoryCache returns a ref to the repoCache interface 114func RepositoryCache() Repo { 115 return rCache 116} 117 118func init() { 119 rCache = &repoCache{ 120 Repositories: make(map[string]repository), 121 Layers: make(map[string]string), 122 images: make(map[string]string), 123 referencesByIDCache: make(map[string]map[string]reference.Named), 124 } 125} 126 127// NewRespositoryCache will create a new repoCache or rehydrate 128// an existing repoCache from the portlayer k/v store 129func NewRepositoryCache(client *client.PortLayer) error { 130 rCache.client = client 131 132 val, err := kv.Get(client, repoKey) 133 if err != nil && err != kv.ErrKeyNotFound { 134 return err 135 } 136 if val != "" { 137 if err = json.Unmarshal([]byte(val), rCache); err != nil { 138 return fmt.Errorf("Failed to unmarshal repository cache: %s", err) 139 } 140 // hydrate refByIDCache 141 for _, repository := range rCache.Repositories { 142 for refStr, refID := range repository { 143 // #nosec: Errors unhandled. 144 ref, _ := reference.ParseNamed(refStr) 145 if rCache.referencesByIDCache[refID] == nil { 146 rCache.referencesByIDCache[refID] = make(map[string]reference.Named) 147 } 148 rCache.referencesByIDCache[refID][refStr] = ref 149 } 150 } 151 // hydrate image -> layer cache 152 for image, layer := range rCache.Layers { 153 rCache.images[image] = layer 154 } 155 156 log.Infof("found %d repositories", len(rCache.Repositories)) 157 log.Infof("found %d image layers", len(rCache.Layers)) 158 } 159 return nil 160} 161 162// Save will persist the repository cache to the 163// portlayer k/v 164func (store *repoCache) Save() error { 165 b, err := json.Marshal(store) 166 if err != nil { 167 log.Errorf("Unable to marshal repository cache: %s", err.Error()) 168 return err 169 } 170 171 err = kv.Put(store.client, repoKey, string(b)) 172 if err != nil { 173 log.Errorf("Unable to save repository cache: %s", err.Error()) 174 return err 175 } 176 177 return nil 178} 179 180func (store *repoCache) AddReference(ref reference.Named, imageID string, force bool, layerID string, save bool) error { 181 if ref.Name() == string(digest.Canonical) { 182 return errors.New("refusing to create an ambiguous tag using digest algorithm as name") 183 } 184 var err error 185 store.mu.Lock() 186 defer store.mu.Unlock() 187 188 // does this repo (i.e. busybox) exist? 189 repository, exists := store.Repositories[ref.Name()] 190 if !exists || repository == nil { 191 repository = make(map[string]string) 192 store.Repositories[ref.Name()] = repository 193 } 194 195 refStr := ref.String() 196 oldID, exists := repository[refStr] 197 198 if exists { 199 if oldID == imageID { 200 log.Debugf("Image %s is already tagged as %s", oldID, ref.String()) 201 return nil 202 } 203 204 // force only works for tags 205 if digested, isDigest := ref.(reference.Canonical); isDigest { 206 log.Debugf("Unable to overwrite %s with digest %s", oldID, digested.Digest().String()) 207 208 return fmt.Errorf("Cannot overwrite digest %s", digested.Digest().String()) 209 } 210 211 if !force { 212 log.Debugf("Refusing to overwrite %s with %s unless force is specified", oldID, ref.String()) 213 214 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) 215 } 216 217 if store.referencesByIDCache[oldID] != nil { 218 delete(store.referencesByIDCache[oldID], refStr) 219 if len(store.referencesByIDCache[oldID]) == 0 { 220 delete(store.referencesByIDCache, oldID) 221 } 222 } 223 } 224 225 repository[refStr] = imageID 226 if store.referencesByIDCache[imageID] == nil { 227 store.referencesByIDCache[imageID] = make(map[string]reference.Named) 228 } 229 store.referencesByIDCache[imageID][refStr] = ref 230 231 if layerID != "" { 232 store.Layers[layerID] = imageID 233 store.images[imageID] = layerID 234 } 235 // should we save this input? 236 if save { 237 err = store.Save() 238 } 239 240 return err 241} 242 243// Remove is a convenience function to allow the passing of a properly 244// formed string that can be parsed into a Named object. 245// 246// Examples: 247// Tags: busybox:1.25.1 248// Digest: nginx@sha256:7281cf7c854b0dfc7c68a6a4de9a785a973a14f1481bc028e2022bcd6a8d9f64 249func (store *repoCache) Remove(ref string, save bool) (string, error) { 250 n, err := reference.ParseNamed(ref) 251 if err != nil { 252 return "", err 253 } 254 255 _, err = store.Delete(n, save) 256 if err != nil { 257 return "", err 258 } 259 260 return n.String(), nil 261} 262 263// Delete deletes a reference from the store. It returns true if a deletion 264// happened, or false otherwise. 265func (store *repoCache) Delete(ref reference.Named, save bool) (bool, error) { 266 ref = reference.WithDefaultTag(ref) 267 268 store.mu.Lock() 269 defer store.mu.Unlock() 270 var err error 271 // return code -- assume success 272 rtc := true 273 repoName := ref.Name() 274 275 repository, exists := store.Repositories[repoName] 276 if !exists { 277 return false, ErrDoesNotExist 278 } 279 refStr := ref.String() 280 if imageID, exists := repository[refStr]; exists { 281 delete(repository, refStr) 282 if len(repository) == 0 { 283 delete(store.Repositories, repoName) 284 } 285 if store.referencesByIDCache[imageID] != nil { 286 delete(store.referencesByIDCache[imageID], refStr) 287 if len(store.referencesByIDCache[imageID]) == 0 { 288 delete(store.referencesByIDCache, imageID) 289 } 290 } 291 if layer, exists := store.images[imageID]; exists { 292 delete(store.Layers, imageID) 293 delete(store.images, layer) 294 } 295 if save { 296 err = store.Save() 297 if err != nil { 298 rtc = false 299 } 300 } 301 return rtc, err 302 } 303 304 return false, ErrDoesNotExist 305} 306 307// GetImageID will return the imageID associated with the 308// specified layerID 309func (store *repoCache) GetImageID(layerID string) string { 310 var imageID string 311 store.mu.RLock() 312 defer store.mu.RUnlock() 313 if image, exists := store.Layers[layerID]; exists { 314 imageID = image 315 } 316 return imageID 317} 318 319// Get returns the imageID for a parsed reference 320func (store *repoCache) Get(ref reference.Named) (string, error) { 321 ref = reference.WithDefaultTag(ref) 322 323 store.mu.RLock() 324 defer store.mu.RUnlock() 325 326 repository, exists := store.Repositories[ref.Name()] 327 if !exists || repository == nil { 328 return "", ErrDoesNotExist 329 } 330 imageID, exists := repository[ref.String()] 331 if !exists { 332 return "", ErrDoesNotExist 333 } 334 335 return imageID, nil 336} 337 338// Tags returns a slice of tags for the specified imageID 339func (store *repoCache) Tags(imageID string) []string { 340 store.mu.RLock() 341 defer store.mu.RUnlock() 342 var tags []string 343 for _, ref := range store.referencesByIDCache[imageID] { 344 if tagged, isTagged := ref.(reference.NamedTagged); isTagged { 345 tags = append(tags, tagged.String()) 346 } 347 } 348 return tags 349} 350 351// Digests returns a slice of digests for the specified imageID 352func (store *repoCache) Digests(imageID string) []string { 353 store.mu.RLock() 354 defer store.mu.RUnlock() 355 var digests []string 356 for _, ref := range store.referencesByIDCache[imageID] { 357 if d, isCanonical := ref.(reference.Canonical); isCanonical { 358 digests = append(digests, d.String()) 359 } 360 } 361 return digests 362} 363 364// References returns a slice of references to the given imageID. The slice 365// will be nil if there are no references to this imageID. 366func (store *repoCache) References(imageID string) []reference.Named { 367 store.mu.RLock() 368 defer store.mu.RUnlock() 369 370 // Convert the internal map to an array for two reasons: 371 // 1) We must not return a mutable 372 // 2) It would be ugly to expose the extraneous map keys to callers. 373 374 var references []reference.Named 375 for _, ref := range store.referencesByIDCache[imageID] { 376 references = append(references, ref) 377 } 378 379 sort.Sort(lexicalRefs(references)) 380 381 return references 382} 383 384// ReferencesByName returns the references for a given repository name. 385// If there are no references known for this repository name, 386// ReferencesByName returns nil. 387func (store *repoCache) ReferencesByName(ref reference.Named) []Association { 388 store.mu.RLock() 389 defer store.mu.RUnlock() 390 391 repository, exists := store.Repositories[ref.Name()] 392 if !exists { 393 return nil 394 } 395 396 var associations []Association 397 for refStr, refID := range repository { 398 ref, err := reference.ParseNamed(refStr) 399 if err != nil { 400 // Should never happen 401 return nil 402 } 403 associations = append(associations, 404 Association{ 405 Ref: ref, 406 ImageID: refID, 407 }) 408 } 409 410 sort.Sort(lexicalAssociations(associations)) 411 412 return associations 413} 414