
Replace metadata.GCStats with an interface for exposing elapsed time Signed-off-by: Daniel Nephin <dnephin@gmail.com>
203 lines
5.4 KiB
Go
203 lines
5.4 KiB
Go
package images
|
|
|
|
import (
|
|
gocontext "context"
|
|
|
|
"github.com/boltdb/bolt"
|
|
eventstypes "github.com/containerd/containerd/api/events"
|
|
imagesapi "github.com/containerd/containerd/api/services/images/v1"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/events"
|
|
"github.com/containerd/containerd/gc"
|
|
"github.com/containerd/containerd/images"
|
|
"github.com/containerd/containerd/log"
|
|
"github.com/containerd/containerd/metadata"
|
|
"github.com/containerd/containerd/plugin"
|
|
ptypes "github.com/gogo/protobuf/types"
|
|
"golang.org/x/net/context"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
func init() {
|
|
plugin.Register(&plugin.Registration{
|
|
Type: plugin.GRPCPlugin,
|
|
ID: "images",
|
|
Requires: []plugin.Type{
|
|
plugin.MetadataPlugin,
|
|
plugin.GCPlugin,
|
|
},
|
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
|
m, err := ic.Get(plugin.MetadataPlugin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g, err := ic.Get(plugin.GCPlugin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewService(m.(*metadata.DB), ic.Events, g.(gcScheduler)), nil
|
|
},
|
|
})
|
|
}
|
|
|
|
type gcScheduler interface {
|
|
ScheduleAndWait(gocontext.Context) (gc.Stats, error)
|
|
}
|
|
|
|
type service struct {
|
|
db *metadata.DB
|
|
gc gcScheduler
|
|
publisher events.Publisher
|
|
}
|
|
|
|
// NewService returns the GRPC image server
|
|
func NewService(db *metadata.DB, publisher events.Publisher, gc gcScheduler) imagesapi.ImagesServer {
|
|
return &service{
|
|
db: db,
|
|
gc: gc,
|
|
publisher: publisher,
|
|
}
|
|
}
|
|
|
|
func (s *service) Register(server *grpc.Server) error {
|
|
imagesapi.RegisterImagesServer(server, s)
|
|
return nil
|
|
}
|
|
|
|
func (s *service) Get(ctx context.Context, req *imagesapi.GetImageRequest) (*imagesapi.GetImageResponse, error) {
|
|
var resp imagesapi.GetImageResponse
|
|
|
|
return &resp, errdefs.ToGRPC(s.withStoreView(ctx, func(ctx context.Context, store images.Store) error {
|
|
image, err := store.Get(ctx, req.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
imagepb := imageToProto(&image)
|
|
resp.Image = &imagepb
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) {
|
|
var resp imagesapi.ListImagesResponse
|
|
|
|
return &resp, errdefs.ToGRPC(s.withStoreView(ctx, func(ctx context.Context, store images.Store) error {
|
|
images, err := store.List(ctx, req.Filters...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp.Images = imagesToProto(images)
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
func (s *service) Create(ctx context.Context, req *imagesapi.CreateImageRequest) (*imagesapi.CreateImageResponse, error) {
|
|
log.G(ctx).WithField("name", req.Image.Name).WithField("target", req.Image.Target.Digest).Debugf("create image")
|
|
if req.Image.Name == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "Image.Name required")
|
|
}
|
|
|
|
var (
|
|
image = imageFromProto(&req.Image)
|
|
resp imagesapi.CreateImageResponse
|
|
)
|
|
if err := s.withStoreUpdate(ctx, func(ctx context.Context, store images.Store) error {
|
|
created, err := store.Create(ctx, image)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp.Image = imageToProto(&created)
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errdefs.ToGRPC(err)
|
|
}
|
|
|
|
if err := s.publisher.Publish(ctx, "/images/create", &eventstypes.ImageCreate{
|
|
Name: resp.Image.Name,
|
|
Labels: resp.Image.Labels,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &resp, nil
|
|
|
|
}
|
|
|
|
func (s *service) Update(ctx context.Context, req *imagesapi.UpdateImageRequest) (*imagesapi.UpdateImageResponse, error) {
|
|
if req.Image.Name == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "Image.Name required")
|
|
}
|
|
|
|
var (
|
|
image = imageFromProto(&req.Image)
|
|
resp imagesapi.UpdateImageResponse
|
|
)
|
|
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 err != nil {
|
|
return err
|
|
}
|
|
|
|
resp.Image = imageToProto(&updated)
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errdefs.ToGRPC(err)
|
|
}
|
|
|
|
if err := s.publisher.Publish(ctx, "/images/update", &eventstypes.ImageUpdate{
|
|
Name: resp.Image.Name,
|
|
Labels: resp.Image.Labels,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &resp, nil
|
|
}
|
|
|
|
func (s *service) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest) (*ptypes.Empty, error) {
|
|
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))
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.publisher.Publish(ctx, "/images/delete", &eventstypes.ImageDelete{
|
|
Name: req.Name,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if req.Sync {
|
|
if _, err := s.gc.ScheduleAndWait(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|