package metadata import ( "context" "encoding/binary" "fmt" "strings" "time" "github.com/boltdb/bolt" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/filters" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) type imageStore struct { tx *bolt.Tx } func NewImageStore(tx *bolt.Tx) images.Store { return &imageStore{tx: tx} } func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) { var image images.Image namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return images.Image{}, err } bkt := getImagesBucket(s.tx, namespace) if bkt == nil { return images.Image{}, errors.Wrapf(errdefs.ErrNotFound, "image %q", name) } ibkt := bkt.Bucket([]byte(name)) if ibkt == nil { return images.Image{}, errors.Wrapf(errdefs.ErrNotFound, "image %q", name) } image.Name = name if err := readImage(&image, ibkt); err != nil { return images.Image{}, errors.Wrapf(err, "image %q", name) } return image, nil } func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, error) { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return nil, err } filter, err := filters.ParseAll(fs...) if err != nil { return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error()) } bkt := getImagesBucket(s.tx, namespace) if bkt == nil { return nil, nil // empty store } var m []images.Image if err := bkt.ForEach(func(k, v []byte) error { var ( image = images.Image{ Name: string(k), } kbkt = bkt.Bucket(k) ) if err := readImage(&image, kbkt); err != nil { return err } if filter.Match(adaptImage(image)) { m = append(m, image) } return nil }); err != nil { return nil, err } return m, nil } func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Image, error) { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return images.Image{}, err } if image.Name == "" { return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for create") } return image, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { ibkt, err := bkt.CreateBucket([]byte(image.Name)) if err != nil { if err != bolt.ErrBucketExists { return err } return errors.Wrapf(errdefs.ErrAlreadyExists, "image %q", image.Name) } image.CreatedAt = time.Now().UTC() image.UpdatedAt = image.CreatedAt return writeImage(ibkt, &image) }) } func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths ...string) (images.Image, error) { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return images.Image{}, err } if image.Name == "" { return images.Image{}, errors.Wrapf(errdefs.ErrInvalidArgument, "image name is required for update") } var updated images.Image return updated, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { ibkt := bkt.Bucket([]byte(image.Name)) if ibkt == nil { return errors.Wrapf(errdefs.ErrNotFound, "image %q", image.Name) } if err := readImage(&updated, ibkt); err != nil { return errors.Wrapf(err, "image %q", image.Name) } createdat := updated.CreatedAt updated.Name = image.Name if len(fieldpaths) > 0 { for _, path := range fieldpaths { if strings.HasPrefix(path, "labels.") { if updated.Labels == nil { updated.Labels = map[string]string{} } key := strings.TrimPrefix(path, "labels.") updated.Labels[key] = image.Labels[key] continue } switch path { case "labels": updated.Labels = image.Labels case "target": // NOTE(stevvooe): While we allow setting individual labels, we // only support replacing the target as a unit, since that is // commonly pulled as a unit from other sources. It often doesn't // make sense to modify the size or digest without touching the // mediatype, as well, for example. updated.Target = image.Target default: return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on image %q", path, image.Name) } } } else { updated = image } updated.CreatedAt = createdat updated.UpdatedAt = time.Now().UTC() return writeImage(ibkt, &updated) }) } func (s *imageStore) Delete(ctx context.Context, name string) error { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return err } return withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { err := bkt.DeleteBucket([]byte(name)) if err == bolt.ErrBucketNotFound { return errors.Wrapf(errdefs.ErrNotFound, "image %q", name) } return err }) } func readImage(image *images.Image, bkt *bolt.Bucket) error { if err := readTimestamps(&image.CreatedAt, &image.UpdatedAt, bkt); err != nil { return err } lbkt := bkt.Bucket(bucketKeyLabels) if lbkt != nil { image.Labels = map[string]string{} if err := readLabels(image.Labels, lbkt); err != nil { return err } } tbkt := bkt.Bucket(bucketKeyTarget) if tbkt == nil { return errors.New("unable to read target bucket") } return tbkt.ForEach(func(k, v []byte) error { if v == nil { return nil // skip it? a bkt maybe? } // TODO(stevvooe): This is why we need to use byte values for // keys, rather than full arrays. switch string(k) { case string(bucketKeyDigest): image.Target.Digest = digest.Digest(v) case string(bucketKeyMediaType): image.Target.MediaType = string(v) case string(bucketKeySize): image.Target.Size, _ = binary.Varint(v) } return nil }) } func writeImage(bkt *bolt.Bucket, image *images.Image) error { if err := writeTimestamps(bkt, image.CreatedAt, image.UpdatedAt); err != nil { return err } if err := writeLabels(bkt, image.Labels); err != nil { return errors.Wrapf(err, "writing labels for image %v", image.Name) } // write the target bucket tbkt, err := bkt.CreateBucketIfNotExists([]byte(bucketKeyTarget)) if err != nil { return err } sizeEncoded, err := encodeSize(image.Target.Size) if err != nil { return err } for _, v := range [][2][]byte{ {bucketKeyDigest, []byte(image.Target.Digest)}, {bucketKeyMediaType, []byte(image.Target.MediaType)}, {bucketKeySize, sizeEncoded}, } { if err := tbkt.Put(v[0], v[1]); err != nil { return err } } return nil } func encodeSize(size int64) ([]byte, error) { var ( buf [binary.MaxVarintLen64]byte sizeEncoded []byte = buf[:] ) sizeEncoded = sizeEncoded[:binary.PutVarint(sizeEncoded, size)] if len(sizeEncoded) == 0 { return nil, fmt.Errorf("failed encoding size = %v", size) } return sizeEncoded, nil }