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