Merge pull request #1960 from dmcgowan/images-removal-dirty

metadata: image removal triggers GC
This commit is contained in:
Stephen Day 2018-01-17 13:58:39 -08:00 committed by GitHub
commit acc6011ac1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 246 deletions

View File

@ -106,13 +106,8 @@ func imagesBucketPath(namespace string) [][]byte {
return [][]byte{bucketKeyVersion, []byte(namespace), bucketKeyObjectImages} return [][]byte{bucketKeyVersion, []byte(namespace), bucketKeyObjectImages}
} }
func withImagesBucket(tx *bolt.Tx, namespace string, fn func(bkt *bolt.Bucket) error) error { func createImagesBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
bkt, err := createBucketIfNotExists(tx, imagesBucketPath(namespace)...) return createBucketIfNotExists(tx, imagesBucketPath(namespace)...)
if err != nil {
return err
}
return fn(bkt)
} }
func getImagesBucket(tx *bolt.Tx, namespace string) *bolt.Bucket { func getImagesBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
@ -143,6 +138,10 @@ func createSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) (*bolt.
return bkt, nil return bkt, nil
} }
func getSnapshottersBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectSnapshots)
}
func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Bucket { func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Bucket {
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectSnapshots, []byte(snapshotter)) return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectSnapshots, []byte(snapshotter))
} }

View File

@ -261,7 +261,7 @@ func TestMetadataCollector(t *testing.T) {
if err := mdb.Update(func(tx *bolt.Tx) error { if err := mdb.Update(func(tx *bolt.Tx) error {
for _, obj := range objects { for _, obj := range objects {
node, err := create(obj, tx, cs, sn) node, err := create(obj, tx, NewImageStore(mdb), cs, sn)
if err != nil { if err != nil {
return err return err
} }
@ -336,7 +336,7 @@ func benchmarkTrigger(n int) func(b *testing.B) {
if err := mdb.Update(func(tx *bolt.Tx) error { if err := mdb.Update(func(tx *bolt.Tx) error {
for _, obj := range objects { for _, obj := range objects {
node, err := create(obj, tx, cs, sn) node, err := create(obj, tx, NewImageStore(mdb), cs, sn)
if err != nil { if err != nil {
return err return err
} }
@ -416,7 +416,7 @@ type object struct {
labels map[string]string labels map[string]string
} }
func create(obj object, tx *bolt.Tx, cs content.Store, sn snapshots.Snapshotter) (*gc.Node, error) { func create(obj object, tx *bolt.Tx, is images.Store, cs content.Store, sn snapshots.Snapshotter) (*gc.Node, error) {
var ( var (
node *gc.Node node *gc.Node
namespace = "test" namespace = "test"
@ -469,12 +469,14 @@ func create(obj object, tx *bolt.Tx, cs content.Store, sn snapshots.Snapshotter)
} }
} }
case testImage: case testImage:
ctx := WithTransactionContext(ctx, tx)
image := images.Image{ image := images.Image{
Name: v.name, Name: v.name,
Target: v.target, Target: v.target,
Labels: obj.labels, Labels: obj.labels,
} }
_, err := NewImageStore(tx).Create(ctx, image) _, err := is.Create(ctx, image)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to create image") return nil, errors.Wrap(err, "failed to create image")
} }

View File

@ -20,12 +20,12 @@ import (
) )
type imageStore struct { type imageStore struct {
tx *bolt.Tx db *DB
} }
// NewImageStore returns a store backed by a bolt DB // NewImageStore returns a store backed by a bolt DB
func NewImageStore(tx *bolt.Tx) images.Store { func NewImageStore(db *DB) images.Store {
return &imageStore{tx: tx} return &imageStore{db: db}
} }
func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) { func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) {
@ -36,19 +36,25 @@ func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error)
return images.Image{}, err return images.Image{}, err
} }
bkt := getImagesBucket(s.tx, namespace) if err := view(ctx, s.db, func(tx *bolt.Tx) error {
if bkt == nil { bkt := getImagesBucket(tx, namespace)
return images.Image{}, errors.Wrapf(errdefs.ErrNotFound, "image %q", name) if bkt == nil {
} return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
}
ibkt := bkt.Bucket([]byte(name)) ibkt := bkt.Bucket([]byte(name))
if ibkt == nil { if ibkt == nil {
return images.Image{}, errors.Wrapf(errdefs.ErrNotFound, "image %q", name) return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
} }
image.Name = name image.Name = name
if err := readImage(&image, ibkt); err != nil { if err := readImage(&image, ibkt); err != nil {
return images.Image{}, errors.Wrapf(err, "image %q", name) return errors.Wrapf(err, "image %q", name)
}
return nil
}); err != nil {
return images.Image{}, err
} }
return image, nil return image, nil
@ -65,28 +71,30 @@ func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, er
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error()) 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 var m []images.Image
if err := bkt.ForEach(func(k, v []byte) error { if err := view(ctx, s.db, func(tx *bolt.Tx) error {
var ( bkt := getImagesBucket(tx, namespace)
image = images.Image{ if bkt == nil {
Name: string(k), return nil // empty store
}
return 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
} }
kbkt = bkt.Bucket(k)
)
if err := readImage(&image, kbkt); err != nil { if filter.Match(adaptImage(image)) {
return err m = append(m, image)
} }
return nil
if filter.Match(adaptImage(image)) { })
m = append(m, image)
}
return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@ -100,11 +108,16 @@ func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Ima
return images.Image{}, err return images.Image{}, err
} }
if err := validateImage(&image); err != nil { if err := update(ctx, s.db, func(tx *bolt.Tx) error {
return images.Image{}, err if err := validateImage(&image); err != nil {
} return err
}
bkt, err := createImagesBucket(tx, namespace)
if err != nil {
return err
}
return image, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
ibkt, err := bkt.CreateBucket([]byte(image.Name)) ibkt, err := bkt.CreateBucket([]byte(image.Name))
if err != nil { if err != nil {
if err != bolt.ErrBucketExists { if err != bolt.ErrBucketExists {
@ -117,7 +130,11 @@ func (s *imageStore) Create(ctx context.Context, image images.Image) (images.Ima
image.CreatedAt = time.Now().UTC() image.CreatedAt = time.Now().UTC()
image.UpdatedAt = image.CreatedAt image.UpdatedAt = image.CreatedAt
return writeImage(ibkt, &image) return writeImage(ibkt, &image)
}) }); err != nil {
return images.Image{}, err
}
return image, nil
} }
func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths ...string) (images.Image, error) { func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths ...string) (images.Image, error) {
@ -131,7 +148,13 @@ func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths
} }
var updated images.Image var updated images.Image
return updated, withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
if err := update(ctx, s.db, func(tx *bolt.Tx) error {
bkt, err := createImagesBucket(tx, namespace)
if err != nil {
return err
}
ibkt := bkt.Bucket([]byte(image.Name)) ibkt := bkt.Bucket([]byte(image.Name))
if ibkt == nil { if ibkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "image %q", image.Name) return errors.Wrapf(errdefs.ErrNotFound, "image %q", image.Name)
@ -180,7 +203,12 @@ func (s *imageStore) Update(ctx context.Context, image images.Image, fieldpaths
updated.CreatedAt = createdat updated.CreatedAt = createdat
updated.UpdatedAt = time.Now().UTC() updated.UpdatedAt = time.Now().UTC()
return writeImage(ibkt, &updated) return writeImage(ibkt, &updated)
}) }); err != nil {
return images.Image{}, err
}
return updated, nil
} }
func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.DeleteOpt) error { func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.DeleteOpt) error {
@ -189,11 +217,24 @@ func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.Del
return err return err
} }
return withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error { return update(ctx, s.db, func(tx *bolt.Tx) error {
err := bkt.DeleteBucket([]byte(name)) bkt := getImagesBucket(tx, namespace)
if bkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
}
err = bkt.DeleteBucket([]byte(name))
if err == bolt.ErrBucketNotFound { if err == bolt.ErrBucketNotFound {
return errors.Wrapf(errdefs.ErrNotFound, "image %q", name) return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
} }
// A reference to a piece of content has been removed,
// mark content store as dirty for triggering garbage
// collection
s.db.dirtyL.Lock()
s.db.dirtyCS = true
s.db.dirtyL.Unlock()
return err return err
}) })
} }

View File

@ -6,7 +6,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
@ -18,6 +17,7 @@ import (
func TestImagesList(t *testing.T) { func TestImagesList(t *testing.T) {
ctx, db, cancel := testEnv(t) ctx, db, cancel := testEnv(t)
defer cancel() defer cancel()
store := NewImageStore(NewDB(db, nil, nil))
testset := map[string]*images.Image{} testset := map[string]*images.Image{}
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
@ -36,21 +36,15 @@ func TestImagesList(t *testing.T) {
}, },
} }
if err := db.Update(func(tx *bolt.Tx) error { now := time.Now()
store := NewImageStore(tx) result, err := store.Create(ctx, *testset[id])
now := time.Now() if err != nil {
result, err := store.Create(ctx, *testset[id])
if err != nil {
return err
}
checkImageTimestamps(t, &result, now, true)
testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt
checkImagesEqual(t, &result, testset[id], "ensure that containers were created as expected for list")
return nil
}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkImageTimestamps(t, &result, now, true)
testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt
checkImagesEqual(t, &result, testset[id], "ensure that containers were created as expected for list")
} }
for _, testcase := range []struct { for _, testcase := range []struct {
@ -102,46 +96,33 @@ func TestImagesList(t *testing.T) {
testset = newtestset testset = newtestset
} }
if err := db.View(func(tx *bolt.Tx) error { results, err := store.List(ctx, testcase.filters...)
store := NewImageStore(tx) if err != nil {
results, err := store.List(ctx, testcase.filters...)
if err != nil {
t.Fatal(err)
}
if len(results) == 0 { // all tests return a non-empty result set
t.Fatalf("no results returned")
}
if len(results) != len(testset) {
t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset))
}
for _, result := range results {
checkImagesEqual(t, &result, testset[result.Name], "list results did not match")
}
return nil
}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(results) == 0 { // all tests return a non-empty result set
t.Fatalf("no results returned")
}
if len(results) != len(testset) {
t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset))
}
for _, result := range results {
checkImagesEqual(t, &result, testset[result.Name], "list results did not match")
}
}) })
} }
// delete everything to test it // delete everything to test it
for id := range testset { for id := range testset {
if err := db.Update(func(tx *bolt.Tx) error { if err := store.Delete(ctx, id); err != nil {
store := NewImageStore(tx)
return store.Delete(ctx, id)
}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// try it again, get NotFound // try it again, get NotFound
if err := db.Update(func(tx *bolt.Tx) error { if err := store.Delete(ctx, id); errors.Cause(err) != errdefs.ErrNotFound {
store := NewImageStore(tx)
return store.Delete(ctx, id)
}); errors.Cause(err) != errdefs.ErrNotFound {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
} }
@ -149,6 +130,7 @@ func TestImagesList(t *testing.T) {
func TestImagesCreateUpdateDelete(t *testing.T) { func TestImagesCreateUpdateDelete(t *testing.T) {
ctx, db, cancel := testEnv(t) ctx, db, cancel := testEnv(t)
defer cancel() defer cancel()
store := NewImageStore(NewDB(db, nil, nil))
for _, testcase := range []struct { for _, testcase := range []struct {
name string name string
@ -383,78 +365,52 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
testcase.expected.Target.Digest = testcase.original.Target.Digest testcase.expected.Target.Digest = testcase.original.Target.Digest
} }
done := errors.New("test complete") // Create
if err := db.Update(func(tx *bolt.Tx) error { now := time.Now()
var ( created, err := store.Create(ctx, testcase.original)
store = NewImageStore(tx) if errors.Cause(err) != testcase.createerr {
now = time.Now() if testcase.createerr == nil {
) t.Fatalf("unexpected error: %v", err)
} else {
created, err := store.Create(ctx, testcase.original) t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr)
if errors.Cause(err) != testcase.createerr {
if testcase.createerr == nil {
t.Fatalf("unexpected error: %v", err)
} else {
t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr)
}
} else if testcase.createerr != nil {
return done
} }
} else if testcase.createerr != nil {
return
}
checkImageTimestamps(t, &created, now, true) checkImageTimestamps(t, &created, now, true)
testcase.original.CreatedAt = created.CreatedAt testcase.original.CreatedAt = created.CreatedAt
testcase.expected.CreatedAt = created.CreatedAt testcase.expected.CreatedAt = created.CreatedAt
testcase.original.UpdatedAt = created.UpdatedAt testcase.original.UpdatedAt = created.UpdatedAt
testcase.expected.UpdatedAt = created.UpdatedAt testcase.expected.UpdatedAt = created.UpdatedAt
checkImagesEqual(t, &created, &testcase.original, "unexpected image on creation") checkImagesEqual(t, &created, &testcase.original, "unexpected image on creation")
return nil
}); err != nil { // Update
if err == done { now = time.Now()
return updated, err := store.Update(ctx, testcase.input, testcase.fieldpaths...)
if errors.Cause(err) != testcase.cause {
if testcase.cause == nil {
t.Fatalf("unexpected error: %v", err)
} else {
t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause)
} }
} else if testcase.cause != nil {
return
}
checkImageTimestamps(t, &updated, now, false)
testcase.expected.UpdatedAt = updated.UpdatedAt
checkImagesEqual(t, &updated, &testcase.expected, "updated failed to get expected result")
// Get
result, err := store.Get(ctx, testcase.original.Name)
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := db.Update(func(tx *bolt.Tx) error { checkImagesEqual(t, &result, &testcase.expected, "get after failed to get expected result")
now := time.Now()
store := NewImageStore(tx)
updated, err := store.Update(ctx, testcase.input, testcase.fieldpaths...)
if errors.Cause(err) != testcase.cause {
if testcase.cause == nil {
t.Fatalf("unexpected error: %v", err)
} else {
t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause)
}
} else if testcase.cause != nil {
return done
}
checkImageTimestamps(t, &updated, now, false)
testcase.expected.UpdatedAt = updated.UpdatedAt
checkImagesEqual(t, &updated, &testcase.expected, "updated failed to get expected result")
return nil
}); err != nil {
if err == done {
return
}
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
store := NewImageStore(tx)
result, err := store.Get(ctx, testcase.original.Name)
if err != nil {
t.Fatal(err)
}
checkImagesEqual(t, &result, &testcase.expected, "get after failed to get expected result")
return nil
}); err != nil {
t.Fatal(err)
}
}) })
} }
} }

View File

@ -133,31 +133,38 @@ func (s *namespaceStore) Delete(ctx context.Context, namespace string) error {
} }
func (s *namespaceStore) namespaceEmpty(ctx context.Context, namespace string) (bool, error) { func (s *namespaceStore) namespaceEmpty(ctx context.Context, namespace string) (bool, error) {
ctx = namespaces.WithNamespace(ctx, namespace) // Get all data buckets
buckets := []*bolt.Bucket{
// need to check the various object stores. getImagesBucket(s.tx, namespace),
getBlobsBucket(s.tx, namespace),
imageStore := NewImageStore(s.tx) getContainersBucket(s.tx, namespace),
images, err := imageStore.List(ctx)
if err != nil {
return false, err
} }
if len(images) > 0 { if snbkt := getSnapshottersBucket(s.tx, namespace); snbkt != nil {
return false, nil if err := snbkt.ForEach(func(k, v []byte) error {
if v == nil {
buckets = append(buckets, snbkt.Bucket(k))
}
return nil
}); err != nil {
return false, err
}
} }
containerStore := NewContainerStore(s.tx) // Ensure data buckets are empty
containers, err := containerStore.List(ctx) for _, bkt := range buckets {
if err != nil { if !isBucketEmpty(bkt) {
return false, err return false, nil
}
} }
if len(containers) > 0 {
return false, nil
}
// TODO(stevvooe): Need to add check for content store, as well. Still need
// to make content store namespace aware.
return true, nil return true, nil
} }
func isBucketEmpty(bkt *bolt.Bucket) bool {
if bkt == nil {
return true
}
k, _ := bkt.Cursor().First()
return k == nil
}

View File

@ -3,7 +3,6 @@ package images
import ( import (
gocontext "context" gocontext "context"
"github.com/boltdb/bolt"
eventstypes "github.com/containerd/containerd/api/events" eventstypes "github.com/containerd/containerd/api/events"
imagesapi "github.com/containerd/containerd/api/services/images/v1" imagesapi "github.com/containerd/containerd/api/services/images/v1"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -38,7 +37,7 @@ func init() {
return nil, err return nil, err
} }
return NewService(m.(*metadata.DB), ic.Events, g.(gcScheduler)), nil return NewService(metadata.NewImageStore(m.(*metadata.DB)), ic.Events, g.(gcScheduler)), nil
}, },
}) })
} }
@ -48,15 +47,15 @@ type gcScheduler interface {
} }
type service struct { type service struct {
db *metadata.DB store images.Store
gc gcScheduler gc gcScheduler
publisher events.Publisher publisher events.Publisher
} }
// NewService returns the GRPC image server // NewService returns the GRPC image server
func NewService(db *metadata.DB, publisher events.Publisher, gc gcScheduler) imagesapi.ImagesServer { func NewService(is images.Store, publisher events.Publisher, gc gcScheduler) imagesapi.ImagesServer {
return &service{ return &service{
db: db, store: is,
gc: gc, gc: gc,
publisher: publisher, publisher: publisher,
} }
@ -68,31 +67,26 @@ func (s *service) Register(server *grpc.Server) error {
} }
func (s *service) Get(ctx context.Context, req *imagesapi.GetImageRequest) (*imagesapi.GetImageResponse, error) { func (s *service) Get(ctx context.Context, req *imagesapi.GetImageRequest) (*imagesapi.GetImageResponse, error) {
var resp imagesapi.GetImageResponse image, err := s.store.Get(ctx, req.Name)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
return &resp, errdefs.ToGRPC(s.withStoreView(ctx, func(ctx context.Context, store images.Store) error { imagepb := imageToProto(&image)
image, err := store.Get(ctx, req.Name) return &imagesapi.GetImageResponse{
if err != nil { Image: &imagepb,
return err }, nil
}
imagepb := imageToProto(&image)
resp.Image = &imagepb
return nil
}))
} }
func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) { func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) {
var resp imagesapi.ListImagesResponse images, err := s.store.List(ctx, req.Filters...)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
return &resp, errdefs.ToGRPC(s.withStoreView(ctx, func(ctx context.Context, store images.Store) error { return &imagesapi.ListImagesResponse{
images, err := store.List(ctx, req.Filters...) Images: imagesToProto(images),
if err != nil { }, nil
return err
}
resp.Images = imagesToProto(images)
return nil
}))
} }
func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest) (*imagesapi.CreateImageResponse, error) { func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest) (*imagesapi.CreateImageResponse, error) {
@ -105,18 +99,13 @@ func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest)
image = imageFromProto(&req.Image) image = imageFromProto(&req.Image)
resp imagesapi.CreateImageResponse resp imagesapi.CreateImageResponse
) )
if err := s.withStoreUpdate(ctx, func(ctx context.Context, store images.Store) error { created, err := s.store.Create(ctx, image)
created, err := store.Create(ctx, image) if err != nil {
if err != nil {
return err
}
resp.Image = imageToProto(&created)
return nil
}); err != nil {
return nil, errdefs.ToGRPC(err) return nil, errdefs.ToGRPC(err)
} }
resp.Image = imageToProto(&created)
if err := s.publisher.Publish(ctx, "/images/create", &eventstypes.ImageCreate{ if err := s.publisher.Publish(ctx, "/images/create", &eventstypes.ImageCreate{
Name: resp.Image.Name, Name: resp.Image.Name,
Labels: resp.Image.Labels, Labels: resp.Image.Labels,
@ -134,28 +123,24 @@ func (s *service) Update(ctx context.Context, req *imagesapi.UpdateImageRequest)
} }
var ( var (
image = imageFromProto(&req.Image) image = imageFromProto(&req.Image)
resp imagesapi.UpdateImageResponse resp imagesapi.UpdateImageResponse
fieldpaths []string
) )
if err := s.withStoreUpdate(ctx, func(ctx context.Context, store images.Store) error {
var fieldpaths []string
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
for _, path := range req.UpdateMask.Paths {
fieldpaths = append(fieldpaths, path)
}
}
updated, err := store.Update(ctx, image, fieldpaths...) if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
if err != nil { for _, path := range req.UpdateMask.Paths {
return err fieldpaths = append(fieldpaths, path)
} }
}
resp.Image = imageToProto(&updated) updated, err := s.store.Update(ctx, image, fieldpaths...)
return nil if err != nil {
}); err != nil {
return nil, errdefs.ToGRPC(err) return nil, errdefs.ToGRPC(err)
} }
resp.Image = imageToProto(&updated)
if err := s.publisher.Publish(ctx, "/images/update", &eventstypes.ImageUpdate{ if err := s.publisher.Publish(ctx, "/images/update", &eventstypes.ImageUpdate{
Name: resp.Image.Name, Name: resp.Image.Name,
Labels: resp.Image.Labels, Labels: resp.Image.Labels,
@ -168,10 +153,9 @@ func (s *service) Update(ctx context.Context, req *imagesapi.UpdateImageRequest)
func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest) (*ptypes.Empty, error) { func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest) (*ptypes.Empty, error) {
log.G(ctx).WithField("name", req.Name).Debugf("delete image") log.G(ctx).WithField("name", req.Name).Debugf("delete image")
if err := s.withStoreUpdate(ctx, func(ctx context.Context, store images.Store) error {
return errdefs.ToGRPC(store.Delete(ctx, req.Name)) if err := s.store.Delete(ctx, req.Name); err != nil {
}); err != nil { return nil, errdefs.ToGRPC(err)
return nil, err
} }
if err := s.publisher.Publish(ctx, "/images/delete", &eventstypes.ImageDelete{ if err := s.publisher.Publish(ctx, "/images/delete", &eventstypes.ImageDelete{
@ -188,15 +172,3 @@ func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest)
return &ptypes.Empty{}, nil return &ptypes.Empty{}, nil
} }
func (s *service) withStore(ctx context.Context, fn func(ctx context.Context, store images.Store) error) func(tx *bolt.Tx) error {
return func(tx *bolt.Tx) error { return fn(ctx, metadata.NewImageStore(tx)) }
}
func (s *service) withStoreView(ctx context.Context, fn func(ctx context.Context, store images.Store) error) error {
return s.db.View(s.withStore(ctx, fn))
}
func (s *service) withStoreUpdate(ctx context.Context, fn func(ctx context.Context, store images.Store) error) error {
return s.db.Update(s.withStore(ctx, fn))
}