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