1package image
2
3import (
4	"encoding/json"
5	"fmt"
6	"strings"
7	"sync"
8	"time"
9
10	"github.com/docker/distribution/digestset"
11	"github.com/docker/docker/layer"
12	"github.com/docker/docker/pkg/system"
13	"github.com/opencontainers/go-digest"
14	"github.com/pkg/errors"
15	"github.com/sirupsen/logrus"
16)
17
18// Store is an interface for creating and accessing images
19type Store interface {
20	Create(config []byte) (ID, error)
21	Get(id ID) (*Image, error)
22	Delete(id ID) ([]layer.Metadata, error)
23	Search(partialID string) (ID, error)
24	SetParent(id ID, parent ID) error
25	GetParent(id ID) (ID, error)
26	SetLastUpdated(id ID) error
27	GetLastUpdated(id ID) (time.Time, error)
28	Children(id ID) []ID
29	Map() map[ID]*Image
30	Heads() map[ID]*Image
31}
32
33// LayerGetReleaser is a minimal interface for getting and releasing images.
34type LayerGetReleaser interface {
35	Get(layer.ChainID) (layer.Layer, error)
36	Release(layer.Layer) ([]layer.Metadata, error)
37}
38
39type imageMeta struct {
40	layer    layer.Layer
41	children map[ID]struct{}
42}
43
44type store struct {
45	sync.RWMutex
46	ls        LayerGetReleaser
47	images    map[ID]*imageMeta
48	fs        StoreBackend
49	digestSet *digestset.Set
50	os        string
51}
52
53// NewImageStore returns new store object for given layer store
54func NewImageStore(fs StoreBackend, os string, ls LayerGetReleaser) (Store, error) {
55	is := &store{
56		ls:        ls,
57		images:    make(map[ID]*imageMeta),
58		fs:        fs,
59		digestSet: digestset.NewSet(),
60		os:        os,
61	}
62
63	// load all current images and retain layers
64	if err := is.restore(); err != nil {
65		return nil, err
66	}
67
68	return is, nil
69}
70
71func (is *store) restore() error {
72	err := is.fs.Walk(func(dgst digest.Digest) error {
73		img, err := is.Get(IDFromDigest(dgst))
74		if err != nil {
75			logrus.Errorf("invalid image %v, %v", dgst, err)
76			return nil
77		}
78		var l layer.Layer
79		if chainID := img.RootFS.ChainID(); chainID != "" {
80			l, err = is.ls.Get(chainID)
81			if err != nil {
82				return err
83			}
84		}
85		if err := is.digestSet.Add(dgst); err != nil {
86			return err
87		}
88
89		imageMeta := &imageMeta{
90			layer:    l,
91			children: make(map[ID]struct{}),
92		}
93
94		is.images[IDFromDigest(dgst)] = imageMeta
95
96		return nil
97	})
98	if err != nil {
99		return err
100	}
101
102	// Second pass to fill in children maps
103	for id := range is.images {
104		if parent, err := is.GetParent(id); err == nil {
105			if parentMeta := is.images[parent]; parentMeta != nil {
106				parentMeta.children[id] = struct{}{}
107			}
108		}
109	}
110
111	return nil
112}
113
114func (is *store) Create(config []byte) (ID, error) {
115	var img Image
116	err := json.Unmarshal(config, &img)
117	if err != nil {
118		return "", err
119	}
120
121	// TODO @jhowardmsft - LCOW Support. This will need revisiting when coalescing the image stores.
122	// Integrity check - ensure we are creating something for the correct platform
123	if system.LCOWSupported() {
124		if strings.ToLower(img.OperatingSystem()) != strings.ToLower(is.os) {
125			return "", fmt.Errorf("cannot create entry for operating system %q in image store for operating system %q", img.OperatingSystem(), is.os)
126		}
127	}
128
129	// Must reject any config that references diffIDs from the history
130	// which aren't among the rootfs layers.
131	rootFSLayers := make(map[layer.DiffID]struct{})
132	for _, diffID := range img.RootFS.DiffIDs {
133		rootFSLayers[diffID] = struct{}{}
134	}
135
136	layerCounter := 0
137	for _, h := range img.History {
138		if !h.EmptyLayer {
139			layerCounter++
140		}
141	}
142	if layerCounter > len(img.RootFS.DiffIDs) {
143		return "", errors.New("too many non-empty layers in History section")
144	}
145
146	dgst, err := is.fs.Set(config)
147	if err != nil {
148		return "", err
149	}
150	imageID := IDFromDigest(dgst)
151
152	is.Lock()
153	defer is.Unlock()
154
155	if _, exists := is.images[imageID]; exists {
156		return imageID, nil
157	}
158
159	layerID := img.RootFS.ChainID()
160
161	var l layer.Layer
162	if layerID != "" {
163		l, err = is.ls.Get(layerID)
164		if err != nil {
165			return "", errors.Wrapf(err, "failed to get layer %s", layerID)
166		}
167	}
168
169	imageMeta := &imageMeta{
170		layer:    l,
171		children: make(map[ID]struct{}),
172	}
173
174	is.images[imageID] = imageMeta
175	if err := is.digestSet.Add(imageID.Digest()); err != nil {
176		delete(is.images, imageID)
177		return "", err
178	}
179
180	return imageID, nil
181}
182
183type imageNotFoundError string
184
185func (e imageNotFoundError) Error() string {
186	return "No such image: " + string(e)
187}
188
189func (imageNotFoundError) NotFound() {}
190
191func (is *store) Search(term string) (ID, error) {
192	dgst, err := is.digestSet.Lookup(term)
193	if err != nil {
194		if err == digestset.ErrDigestNotFound {
195			err = imageNotFoundError(term)
196		}
197		return "", errors.WithStack(err)
198	}
199	return IDFromDigest(dgst), nil
200}
201
202func (is *store) Get(id ID) (*Image, error) {
203	// todo: Check if image is in images
204	// todo: Detect manual insertions and start using them
205	config, err := is.fs.Get(id.Digest())
206	if err != nil {
207		return nil, err
208	}
209
210	img, err := NewFromJSON(config)
211	if err != nil {
212		return nil, err
213	}
214	img.computedID = id
215
216	img.Parent, err = is.GetParent(id)
217	if err != nil {
218		img.Parent = ""
219	}
220
221	return img, nil
222}
223
224func (is *store) Delete(id ID) ([]layer.Metadata, error) {
225	is.Lock()
226	defer is.Unlock()
227
228	imageMeta := is.images[id]
229	if imageMeta == nil {
230		return nil, fmt.Errorf("unrecognized image ID %s", id.String())
231	}
232	for id := range imageMeta.children {
233		is.fs.DeleteMetadata(id.Digest(), "parent")
234	}
235	if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
236		delete(is.images[parent].children, id)
237	}
238
239	if err := is.digestSet.Remove(id.Digest()); err != nil {
240		logrus.Errorf("error removing %s from digest set: %q", id, err)
241	}
242	delete(is.images, id)
243	is.fs.Delete(id.Digest())
244
245	if imageMeta.layer != nil {
246		return is.ls.Release(imageMeta.layer)
247	}
248	return nil, nil
249}
250
251func (is *store) SetParent(id, parent ID) error {
252	is.Lock()
253	defer is.Unlock()
254	parentMeta := is.images[parent]
255	if parentMeta == nil {
256		return fmt.Errorf("unknown parent image ID %s", parent.String())
257	}
258	if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
259		delete(is.images[parent].children, id)
260	}
261	parentMeta.children[id] = struct{}{}
262	return is.fs.SetMetadata(id.Digest(), "parent", []byte(parent))
263}
264
265func (is *store) GetParent(id ID) (ID, error) {
266	d, err := is.fs.GetMetadata(id.Digest(), "parent")
267	if err != nil {
268		return "", err
269	}
270	return ID(d), nil // todo: validate?
271}
272
273// SetLastUpdated time for the image ID to the current time
274func (is *store) SetLastUpdated(id ID) error {
275	lastUpdated := []byte(time.Now().Format(time.RFC3339Nano))
276	return is.fs.SetMetadata(id.Digest(), "lastUpdated", lastUpdated)
277}
278
279// GetLastUpdated time for the image ID
280func (is *store) GetLastUpdated(id ID) (time.Time, error) {
281	bytes, err := is.fs.GetMetadata(id.Digest(), "lastUpdated")
282	if err != nil || len(bytes) == 0 {
283		// No lastUpdated time
284		return time.Time{}, nil
285	}
286	return time.Parse(time.RFC3339Nano, string(bytes))
287}
288
289func (is *store) Children(id ID) []ID {
290	is.RLock()
291	defer is.RUnlock()
292
293	return is.children(id)
294}
295
296func (is *store) children(id ID) []ID {
297	var ids []ID
298	if is.images[id] != nil {
299		for id := range is.images[id].children {
300			ids = append(ids, id)
301		}
302	}
303	return ids
304}
305
306func (is *store) Heads() map[ID]*Image {
307	return is.imagesMap(false)
308}
309
310func (is *store) Map() map[ID]*Image {
311	return is.imagesMap(true)
312}
313
314func (is *store) imagesMap(all bool) map[ID]*Image {
315	is.RLock()
316	defer is.RUnlock()
317
318	images := make(map[ID]*Image)
319
320	for id := range is.images {
321		if !all && len(is.children(id)) > 0 {
322			continue
323		}
324		img, err := is.Get(id)
325		if err != nil {
326			logrus.Errorf("invalid image access: %q, error: %q", id, err)
327			continue
328		}
329		images[id] = img
330	}
331	return images
332}
333