1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package metadata
18
19import (
20	"context"
21	"encoding/binary"
22	"fmt"
23	"strings"
24	"sync/atomic"
25	"time"
26
27	"github.com/containerd/containerd/errdefs"
28	"github.com/containerd/containerd/filters"
29	"github.com/containerd/containerd/images"
30	"github.com/containerd/containerd/labels"
31	"github.com/containerd/containerd/metadata/boltutil"
32	"github.com/containerd/containerd/namespaces"
33	digest "github.com/opencontainers/go-digest"
34	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
35	"github.com/pkg/errors"
36	bolt "go.etcd.io/bbolt"
37)
38
39type imageStore struct {
40	db *DB
41}
42
43// NewImageStore returns a store backed by a bolt DB
44func NewImageStore(db *DB) images.Store {
45	return &imageStore{db: db}
46}
47
48func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) {
49	var image images.Image
50
51	namespace, err := namespaces.NamespaceRequired(ctx)
52	if err != nil {
53		return images.Image{}, err
54	}
55
56	if err := view(ctx, s.db, func(tx *bolt.Tx) error {
57		bkt := getImagesBucket(tx, namespace)
58		if bkt == nil {
59			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
60		}
61
62		ibkt := bkt.Bucket([]byte(name))
63		if ibkt == nil {
64			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
65		}
66
67		image.Name = name
68		if err := readImage(&image, ibkt); err != nil {
69			return errors.Wrapf(err, "image %q", name)
70		}
71
72		return nil
73	}); err != nil {
74		return images.Image{}, err
75	}
76
77	return image, nil
78}
79
80func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, error) {
81	namespace, err := namespaces.NamespaceRequired(ctx)
82	if err != nil {
83		return nil, err
84	}
85
86	filter, err := filters.ParseAll(fs...)
87	if err != nil {
88		return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
89	}
90
91	var m []images.Image
92	if err := view(ctx, s.db, func(tx *bolt.Tx) error {
93		bkt := getImagesBucket(tx, namespace)
94		if bkt == nil {
95			return nil // empty store
96		}
97
98		return bkt.ForEach(func(k, v []byte) error {
99			var (
100				image = images.Image{
101					Name: string(k),
102				}
103				kbkt = bkt.Bucket(k)
104			)
105
106			if err := readImage(&image, kbkt); err != nil {
107				return err
108			}
109
110			if filter.Match(adaptImage(image)) {
111				m = append(m, image)
112			}
113			return nil
114		})
115	}); err != nil {
116		return nil, err
117	}
118
119	return m, nil
120}
121
122func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Image, error) {
123	namespace, err := namespaces.NamespaceRequired(ctx)
124	if err != nil {
125		return images.Image{}, err
126	}
127
128	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
129		if err := validateImage(&image); err != nil {
130			return err
131		}
132
133		bkt, err := createImagesBucket(tx, namespace)
134		if err != nil {
135			return err
136		}
137
138		ibkt, err := bkt.CreateBucket([]byte(image.Name))
139		if err != nil {
140			if err != bolt.ErrBucketExists {
141				return err
142			}
143
144			return errors.Wrapf(errdefs.ErrAlreadyExists, "image %q", image.Name)
145		}
146
147		image.CreatedAt = time.Now().UTC()
148		image.UpdatedAt = image.CreatedAt
149		return writeImage(ibkt, &image)
150	}); err != nil {
151		return images.Image{}, err
152	}
153
154	return image, nil
155}
156
157func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths ...string) (images.Image, error) {
158	namespace, err := namespaces.NamespaceRequired(ctx)
159	if err != nil {
160		return images.Image{}, err
161	}
162
163	if image.Name == "" {
164		return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for update")
165	}
166
167	var updated images.Image
168
169	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
170		bkt, err := createImagesBucket(tx, namespace)
171		if err != nil {
172			return err
173		}
174
175		ibkt := bkt.Bucket([]byte(image.Name))
176		if ibkt == nil {
177			return errors.Wrapf(errdefs.ErrNotFound, "image %q", image.Name)
178		}
179
180		if err := readImage(&updated, ibkt); err != nil {
181			return errors.Wrapf(err, "image %q", image.Name)
182		}
183		createdat := updated.CreatedAt
184		updated.Name = image.Name
185
186		if len(fieldpaths) > 0 {
187			for _, path := range fieldpaths {
188				if strings.HasPrefix(path, "labels.") {
189					if updated.Labels == nil {
190						updated.Labels = map[string]string{}
191					}
192
193					key := strings.TrimPrefix(path, "labels.")
194					updated.Labels[key] = image.Labels[key]
195					continue
196				} else if strings.HasPrefix(path, "annotations.") {
197					if updated.Target.Annotations == nil {
198						updated.Target.Annotations = map[string]string{}
199					}
200
201					key := strings.TrimPrefix(path, "annotations.")
202					updated.Target.Annotations[key] = image.Target.Annotations[key]
203					continue
204				}
205
206				switch path {
207				case "labels":
208					updated.Labels = image.Labels
209				case "target":
210					// NOTE(stevvooe): While we allow setting individual labels, we
211					// only support replacing the target as a unit, since that is
212					// commonly pulled as a unit from other sources. It often doesn't
213					// make sense to modify the size or digest without touching the
214					// mediatype, as well, for example.
215					updated.Target = image.Target
216				case "annotations":
217					updated.Target.Annotations = image.Target.Annotations
218				default:
219					return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on image %q", path, image.Name)
220				}
221			}
222		} else {
223			updated = image
224		}
225
226		if err := validateImage(&updated); err != nil {
227			return err
228		}
229
230		updated.CreatedAt = createdat
231		updated.UpdatedAt = time.Now().UTC()
232		return writeImage(ibkt, &updated)
233	}); err != nil {
234		return images.Image{}, err
235	}
236
237	return updated, nil
238
239}
240
241func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.DeleteOpt) error {
242	namespace, err := namespaces.NamespaceRequired(ctx)
243	if err != nil {
244		return err
245	}
246
247	return update(ctx, s.db, func(tx *bolt.Tx) error {
248		bkt := getImagesBucket(tx, namespace)
249		if bkt == nil {
250			return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
251		}
252
253		if err = bkt.DeleteBucket([]byte(name)); err != nil {
254			if err == bolt.ErrBucketNotFound {
255				err = errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
256			}
257			return err
258		}
259
260		atomic.AddUint32(&s.db.dirty, 1)
261
262		return nil
263	})
264}
265
266func validateImage(image *images.Image) error {
267	if image.Name == "" {
268		return errors.Wrapf(errdefs.ErrInvalidArgument, "image name must not be empty")
269	}
270
271	for k, v := range image.Labels {
272		if err := labels.Validate(k, v); err != nil {
273			return errors.Wrapf(err, "image.Labels")
274		}
275	}
276
277	return validateTarget(&image.Target)
278}
279
280func validateTarget(target *ocispec.Descriptor) error {
281	// NOTE(stevvooe): Only validate fields we actually store.
282
283	if err := target.Digest.Validate(); err != nil {
284		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.Digest %q invalid: %v", target.Digest, err)
285	}
286
287	if target.Size <= 0 {
288		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.Size must be greater than zero")
289	}
290
291	if target.MediaType == "" {
292		return errors.Wrapf(errdefs.ErrInvalidArgument, "Target.MediaType must be set")
293	}
294
295	return nil
296}
297
298func readImage(image *images.Image, bkt *bolt.Bucket) error {
299	if err := boltutil.ReadTimestamps(bkt, &image.CreatedAt, &image.UpdatedAt); err != nil {
300		return err
301	}
302
303	labels, err := boltutil.ReadLabels(bkt)
304	if err != nil {
305		return err
306	}
307	image.Labels = labels
308
309	image.Target.Annotations, err = boltutil.ReadAnnotations(bkt)
310	if err != nil {
311		return err
312	}
313
314	tbkt := bkt.Bucket(bucketKeyTarget)
315	if tbkt == nil {
316		return errors.New("unable to read target bucket")
317	}
318	return tbkt.ForEach(func(k, v []byte) error {
319		if v == nil {
320			return nil // skip it? a bkt maybe?
321		}
322
323		// TODO(stevvooe): This is why we need to use byte values for
324		// keys, rather than full arrays.
325		switch string(k) {
326		case string(bucketKeyDigest):
327			image.Target.Digest = digest.Digest(v)
328		case string(bucketKeyMediaType):
329			image.Target.MediaType = string(v)
330		case string(bucketKeySize):
331			image.Target.Size, _ = binary.Varint(v)
332		}
333
334		return nil
335	})
336}
337
338func writeImage(bkt *bolt.Bucket, image *images.Image) error {
339	if err := boltutil.WriteTimestamps(bkt, image.CreatedAt, image.UpdatedAt); err != nil {
340		return err
341	}
342
343	if err := boltutil.WriteLabels(bkt, image.Labels); err != nil {
344		return errors.Wrapf(err, "writing labels for image %v", image.Name)
345	}
346
347	if err := boltutil.WriteAnnotations(bkt, image.Target.Annotations); err != nil {
348		return errors.Wrapf(err, "writing Annotations for image %v", image.Name)
349	}
350
351	// write the target bucket
352	tbkt, err := bkt.CreateBucketIfNotExists(bucketKeyTarget)
353	if err != nil {
354		return err
355	}
356
357	sizeEncoded, err := encodeInt(image.Target.Size)
358	if err != nil {
359		return err
360	}
361
362	for _, v := range [][2][]byte{
363		{bucketKeyDigest, []byte(image.Target.Digest)},
364		{bucketKeyMediaType, []byte(image.Target.MediaType)},
365		{bucketKeySize, sizeEncoded},
366	} {
367		if err := tbkt.Put(v[0], v[1]); err != nil {
368			return err
369		}
370	}
371
372	return nil
373}
374
375func encodeInt(i int64) ([]byte, error) {
376	var (
377		buf      [binary.MaxVarintLen64]byte
378		iEncoded = buf[:]
379	)
380	iEncoded = iEncoded[:binary.PutVarint(iEncoded, i)]
381
382	if len(iEncoded) == 0 {
383		return nil, fmt.Errorf("failed encoding integer = %v", i)
384	}
385	return iEncoded, nil
386}
387