images, services/images: implement image service
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>
This commit is contained in:
parent
a5c9d6d41b
commit
1ea809dc2a
5 changed files with 280 additions and 45 deletions
60
services/images/client.go
Normal file
60
services/images/client.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||
"github.com/containerd/containerd/images"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type remoteStore struct {
|
||||
client imagesapi.ImagesClient
|
||||
}
|
||||
|
||||
func NewStoreFromClient(client imagesapi.ImagesClient) images.Store {
|
||||
return &remoteStore{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *remoteStore) Put(ctx context.Context, name string, desc ocispec.Descriptor) error {
|
||||
// TODO(stevvooe): Consider that the remote may want to augment and return
|
||||
// a modified image.
|
||||
_, err := s.client.Put(ctx, &imagesapi.PutRequest{
|
||||
Image: imagesapi.Image{
|
||||
Name: name,
|
||||
Target: descToProto(&desc),
|
||||
},
|
||||
})
|
||||
|
||||
return rewriteGRPCError(err)
|
||||
}
|
||||
|
||||
func (s *remoteStore) Get(ctx context.Context, name string) (images.Image, error) {
|
||||
resp, err := s.client.Get(ctx, &imagesapi.GetRequest{
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return images.Image{}, rewriteGRPCError(err)
|
||||
}
|
||||
|
||||
return imageFromProto(resp.Image), nil
|
||||
}
|
||||
|
||||
func (s *remoteStore) List(ctx context.Context) ([]images.Image, error) {
|
||||
resp, err := s.client.List(ctx, &imagesapi.ListRequest{})
|
||||
if err != nil {
|
||||
return nil, rewriteGRPCError(err)
|
||||
}
|
||||
|
||||
return imagesFromProto(resp.Images), nil
|
||||
}
|
||||
|
||||
func (s *remoteStore) Delete(ctx context.Context, name string) error {
|
||||
_, err := s.client.Delete(ctx, &imagesapi.DeleteRequest{
|
||||
Name: name,
|
||||
})
|
||||
|
||||
return rewriteGRPCError(err)
|
||||
}
|
87
services/images/helpers.go
Normal file
87
services/images/helpers.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||
"github.com/containerd/containerd/api/types/descriptor"
|
||||
"github.com/containerd/containerd/images"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func imagesToProto(images []images.Image) []imagesapi.Image {
|
||||
var imagespb []imagesapi.Image
|
||||
|
||||
for _, image := range images {
|
||||
imagespb = append(imagespb, imageToProto(&image))
|
||||
}
|
||||
|
||||
return imagespb
|
||||
}
|
||||
|
||||
func imagesFromProto(imagespb []imagesapi.Image) []images.Image {
|
||||
var images []images.Image
|
||||
|
||||
for _, image := range imagespb {
|
||||
images = append(images, imageFromProto(&image))
|
||||
}
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
func imageToProto(image *images.Image) imagesapi.Image {
|
||||
return imagesapi.Image{
|
||||
Name: image.Name,
|
||||
Target: descToProto(&image.Target),
|
||||
}
|
||||
}
|
||||
|
||||
func imageFromProto(imagepb *imagesapi.Image) images.Image {
|
||||
return images.Image{
|
||||
Name: imagepb.Name,
|
||||
Target: descFromProto(&imagepb.Target),
|
||||
}
|
||||
}
|
||||
|
||||
func descFromProto(desc *descriptor.Descriptor) ocispec.Descriptor {
|
||||
return ocispec.Descriptor{
|
||||
MediaType: desc.MediaType,
|
||||
Size: desc.Size_,
|
||||
Digest: desc.Digest,
|
||||
}
|
||||
}
|
||||
|
||||
func descToProto(desc *ocispec.Descriptor) descriptor.Descriptor {
|
||||
return descriptor.Descriptor{
|
||||
MediaType: desc.MediaType,
|
||||
Size_: desc.Size,
|
||||
Digest: desc.Digest,
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteGRPCError(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch grpc.Code(errors.Cause(err)) {
|
||||
case codes.AlreadyExists:
|
||||
return images.ErrExists
|
||||
case codes.NotFound:
|
||||
return images.ErrNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func mapGRPCError(err error, id string) error {
|
||||
switch {
|
||||
case images.IsNotFound(err):
|
||||
return grpc.Errorf(codes.NotFound, "image %v not found", id)
|
||||
case images.IsExists(err):
|
||||
return grpc.Errorf(codes.AlreadyExists, "image %v already exists", id)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,32 +1,91 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
imagesapi "github.com/docker/containerd/api/services/images"
|
||||
"github.com/docker/containerd/images"
|
||||
"github.com/boltdb/bolt"
|
||||
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register("images-grpc", &plugin.Registration{
|
||||
Type: plugin.GRPCPlugin,
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return NewService(ic.Meta), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
store images.Store
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
func NewService(store images.Store) imagesapi.ImagesServer {
|
||||
return &Service{store: store}
|
||||
func NewService(db *bolt.DB) imagesapi.ImagesServer {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
func (s *Service) Get(context.Context, *imagesapi.GetRequest) (*imagesapi.GetResponse, error) {
|
||||
panic("not implemented")
|
||||
func (s *Service) Register(server *grpc.Server) error {
|
||||
imagesapi.RegisterImagesServer(server, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Register(context.Context, *imagesapi.RegisterRequest) (*imagesapi.RegisterRequest, error) {
|
||||
panic("not implemented")
|
||||
func (s *Service) Get(ctx context.Context, req *imagesapi.GetRequest) (*imagesapi.GetResponse, error) {
|
||||
var resp imagesapi.GetResponse
|
||||
|
||||
return &resp, s.withStoreTx(ctx, req.Name, false, func(ctx context.Context, store images.Store) error {
|
||||
image, err := store.Get(ctx, req.Name)
|
||||
if err != nil {
|
||||
return mapGRPCError(err, req.Name)
|
||||
}
|
||||
imagepb := imageToProto(&image)
|
||||
resp.Image = &imagepb
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) List(context.Context, *imagesapi.ListRequest) (*imagesapi.ListResponse, error) {
|
||||
panic("not implemented")
|
||||
func (s *Service) Put(ctx context.Context, req *imagesapi.PutRequest) (*empty.Empty, error) {
|
||||
return &empty.Empty{}, s.withStoreTx(ctx, req.Image.Name, true, func(ctx context.Context, store images.Store) error {
|
||||
return mapGRPCError(store.Put(ctx, req.Image.Name, descFromProto(&req.Image.Target)), req.Image.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Delete(context.Context, *imagesapi.DeleteRequest) (*empty.Empty, error) {
|
||||
panic("not implemented")
|
||||
func (s *Service) List(ctx context.Context, _ *imagesapi.ListRequest) (*imagesapi.ListResponse, error) {
|
||||
var resp imagesapi.ListResponse
|
||||
|
||||
return &resp, s.withStoreTx(ctx, "", false, func(ctx context.Context, store images.Store) error {
|
||||
images, err := store.List(ctx)
|
||||
if err != nil {
|
||||
return mapGRPCError(err, "")
|
||||
}
|
||||
|
||||
resp.Images = imagesToProto(images)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, req *imagesapi.DeleteRequest) (*empty.Empty, error) {
|
||||
return &empty.Empty{}, s.withStoreTx(ctx, req.Name, true, func(ctx context.Context, store images.Store) error {
|
||||
return mapGRPCError(store.Delete(ctx, req.Name), req.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) withStoreTx(ctx context.Context, id string, writable bool, fn func(ctx context.Context, store images.Store) error) error {
|
||||
tx, err := s.db.Begin(writable)
|
||||
if err != nil {
|
||||
return mapGRPCError(err, id)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := fn(ctx, images.NewImageStore(tx)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if writable {
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue