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