1ea809dc2a
Server and Client images of the image store are now provided. We have created an image metadata interface and converted the bolt functions to implement that interface over an transaction. A remote client implementation is provided that implements the same interface. Signed-off-by: Stephen J Day <stephen.day@docker.com>
215 lines
4.9 KiB
Go
215 lines
4.9 KiB
Go
package images
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/boltdb/bolt"
|
|
"github.com/containerd/containerd/log"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
ErrExists = errors.New("images: exists")
|
|
ErrNotFound = errors.New("images: not found")
|
|
)
|
|
|
|
type Store interface {
|
|
Put(ctx context.Context, name string, desc ocispec.Descriptor) error
|
|
Get(ctx context.Context, name string) (Image, error)
|
|
List(ctx context.Context) ([]Image, error)
|
|
Delete(ctx context.Context, name string) error
|
|
}
|
|
|
|
// IsNotFound returns true if the error is due to a missing image.
|
|
func IsNotFound(err error) bool {
|
|
return errors.Cause(err) == ErrNotFound
|
|
}
|
|
|
|
func IsExists(err error) bool {
|
|
return errors.Cause(err) == ErrExists
|
|
}
|
|
|
|
var (
|
|
bucketKeyStorageVersion = []byte("v1")
|
|
bucketKeyImages = []byte("images")
|
|
bucketKeyDigest = []byte("digest")
|
|
bucketKeyMediaType = []byte("mediatype")
|
|
bucketKeySize = []byte("size")
|
|
)
|
|
|
|
// TODO(stevvooe): This file comprises the data required to implement the
|
|
// "metadata" store. For now, it is bound tightly to the local machine and bolt
|
|
// but we can take this and use it to define a service interface.
|
|
|
|
// InitDB will initialize the database for use. The database must be opened for
|
|
// write and the caller must not be holding an open transaction.
|
|
func InitDB(db *bolt.DB) error {
|
|
log.L.Debug("init db")
|
|
return db.Update(func(tx *bolt.Tx) error {
|
|
_, err := createBucketIfNotExists(tx, bucketKeyStorageVersion, bucketKeyImages)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func NewImageStore(tx *bolt.Tx) Store {
|
|
return &storage{tx: tx}
|
|
}
|
|
|
|
type storage struct {
|
|
tx *bolt.Tx
|
|
}
|
|
|
|
func (s *storage) Get(ctx context.Context, name string) (Image, error) {
|
|
var image Image
|
|
if err := withImageBucket(s.tx, name, func(bkt *bolt.Bucket) error {
|
|
image.Name = name
|
|
return readImage(&image, bkt)
|
|
}); err != nil {
|
|
return Image{}, err
|
|
}
|
|
|
|
return image, nil
|
|
}
|
|
|
|
func (s *storage) Put(ctx context.Context, name string, desc ocispec.Descriptor) error {
|
|
return withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
|
|
ibkt, err := bkt.CreateBucketIfNotExists([]byte(name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
buf [binary.MaxVarintLen64]byte
|
|
sizeEncoded []byte = buf[:]
|
|
)
|
|
sizeEncoded = sizeEncoded[:binary.PutVarint(sizeEncoded, desc.Size)]
|
|
|
|
if len(sizeEncoded) == 0 {
|
|
return fmt.Errorf("failed encoding size = %v", desc.Size)
|
|
}
|
|
|
|
for _, v := range [][2][]byte{
|
|
{bucketKeyDigest, []byte(desc.Digest)},
|
|
{bucketKeyMediaType, []byte(desc.MediaType)},
|
|
{bucketKeySize, sizeEncoded},
|
|
} {
|
|
if err := ibkt.Put(v[0], v[1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (s *storage) List(ctx context.Context) ([]Image, error) {
|
|
var images []Image
|
|
|
|
if err := withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
|
|
return bkt.ForEach(func(k, v []byte) error {
|
|
var (
|
|
image = Image{
|
|
Name: string(k),
|
|
}
|
|
kbkt = bkt.Bucket(k)
|
|
)
|
|
|
|
if err := readImage(&image, kbkt); err != nil {
|
|
return err
|
|
}
|
|
|
|
images = append(images, image)
|
|
return nil
|
|
})
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return images, nil
|
|
}
|
|
|
|
func (s *storage) Delete(ctx context.Context, name string) error {
|
|
return withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
|
|
return bkt.DeleteBucket([]byte(name))
|
|
})
|
|
}
|
|
|
|
func readImage(image *Image, bkt *bolt.Bucket) error {
|
|
return bkt.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 createBucketIfNotExists(tx *bolt.Tx, keys ...[]byte) (*bolt.Bucket, error) {
|
|
bkt, err := tx.CreateBucketIfNotExists(keys[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, key := range keys[1:] {
|
|
bkt, err = bkt.CreateBucketIfNotExists(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return bkt, nil
|
|
}
|
|
|
|
func withImagesBucket(tx *bolt.Tx, fn func(bkt *bolt.Bucket) error) error {
|
|
bkt := getImagesBucket(tx)
|
|
if bkt == nil {
|
|
return ErrNotFound
|
|
}
|
|
|
|
return fn(bkt)
|
|
}
|
|
|
|
func withImageBucket(tx *bolt.Tx, name string, fn func(bkt *bolt.Bucket) error) error {
|
|
bkt := getImageBucket(tx, name)
|
|
if bkt == nil {
|
|
return ErrNotFound
|
|
}
|
|
|
|
return fn(bkt)
|
|
}
|
|
|
|
func getImagesBucket(tx *bolt.Tx) *bolt.Bucket {
|
|
return getBucket(tx, bucketKeyStorageVersion, bucketKeyImages)
|
|
}
|
|
|
|
func getImageBucket(tx *bolt.Tx, name string) *bolt.Bucket {
|
|
return getBucket(tx, bucketKeyStorageVersion, bucketKeyImages, []byte(name))
|
|
}
|
|
|
|
func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket {
|
|
bkt := tx.Bucket(keys[0])
|
|
|
|
for _, key := range keys[1:] {
|
|
if bkt == nil {
|
|
break
|
|
}
|
|
bkt = bkt.Bucket(key)
|
|
}
|
|
|
|
return bkt
|
|
}
|