Add snapshot and diff service
Remove rootfs service in place of snapshot service. Adds diff service for extracting and creating diffs. Diff creation is not yet implemented. This service allows pulling or creating images without needing root access to mount. Additionally in the future this will allow containerd to ensure extractions happen safely in a chroot if needed. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
56
services/diff/client.go
Normal file
56
services/diff/client.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
diffapi "github.com/containerd/containerd/api/services/diff"
|
||||
"github.com/containerd/containerd/api/types/descriptor"
|
||||
mounttypes "github.com/containerd/containerd/api/types/mount"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// NewApplierFromClient returns a new Applier which communicates
|
||||
// over a GRPC connection.
|
||||
func NewApplierFromClient(client diffapi.DiffClient) rootfs.Applier {
|
||||
return &remoteApplier{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type remoteApplier struct {
|
||||
client diffapi.DiffClient
|
||||
}
|
||||
|
||||
func (r *remoteApplier) Apply(ctx context.Context, diff ocispec.Descriptor, mounts []containerd.Mount) (ocispec.Descriptor, error) {
|
||||
req := &diffapi.ApplyRequest{
|
||||
Diff: fromDescriptor(diff),
|
||||
Mounts: fromMounts(mounts),
|
||||
}
|
||||
resp, err := r.client.Apply(ctx, req)
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, err
|
||||
}
|
||||
return toDescriptor(resp.Applied), nil
|
||||
}
|
||||
|
||||
func fromDescriptor(d ocispec.Descriptor) *descriptor.Descriptor {
|
||||
return &descriptor.Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Digest: d.Digest,
|
||||
Size_: d.Size,
|
||||
}
|
||||
}
|
||||
|
||||
func fromMounts(mounts []containerd.Mount) []*mounttypes.Mount {
|
||||
apiMounts := make([]*mounttypes.Mount, len(mounts))
|
||||
for i, m := range mounts {
|
||||
apiMounts[i] = &mounttypes.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
return apiMounts
|
||||
}
|
||||
138
services/diff/service.go
Normal file
138
services/diff/service.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
diffapi "github.com/containerd/containerd/api/services/diff"
|
||||
"github.com/containerd/containerd/api/types/descriptor"
|
||||
mounttypes "github.com/containerd/containerd/api/types/mount"
|
||||
"github.com/containerd/containerd/archive"
|
||||
"github.com/containerd/containerd/archive/compression"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/snapshot"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register("diff-grpc", &plugin.Registration{
|
||||
Type: plugin.GRPCPlugin,
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return newService(ic.Content, ic.Snapshotter)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type service struct {
|
||||
store content.Store
|
||||
snapshotter snapshot.Snapshotter
|
||||
}
|
||||
|
||||
func newService(store content.Store, snapshotter snapshot.Snapshotter) (*service, error) {
|
||||
return &service{
|
||||
store: store,
|
||||
snapshotter: snapshotter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Register(gs *grpc.Server) error {
|
||||
diffapi.RegisterDiffServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi.ApplyResponse, error) {
|
||||
desc := toDescriptor(er.Diff)
|
||||
// TODO: Check for supported media types
|
||||
|
||||
mounts := toMounts(er.Mounts)
|
||||
|
||||
dir, err := ioutil.TempDir("", "extract-")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temporary directory")
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
if err := containerd.MountAll(mounts, dir); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to mount")
|
||||
}
|
||||
defer containerd.Unmount(dir, 0)
|
||||
|
||||
r, err := s.store.Reader(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get reader from content store")
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// TODO: only decompress stream if media type is compressed
|
||||
ds, err := compression.DecompressStream(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ds.Close()
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
rc := &readCounter{
|
||||
r: io.TeeReader(ds, digester.Hash()),
|
||||
}
|
||||
|
||||
if _, err := archive.Apply(ctx, dir, rc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read any trailing data
|
||||
if _, err := io.Copy(ioutil.Discard, rc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &diffapi.ApplyResponse{
|
||||
Applied: &descriptor.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageLayer,
|
||||
Digest: digester.Digest(),
|
||||
Size_: rc.c,
|
||||
},
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) Diff(context.Context, *diffapi.DiffRequest) (*diffapi.DiffResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
type readCounter struct {
|
||||
r io.Reader
|
||||
c int64
|
||||
}
|
||||
|
||||
func (rc *readCounter) Read(p []byte) (n int, err error) {
|
||||
n, err = rc.r.Read(p)
|
||||
rc.c += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func toDescriptor(d *descriptor.Descriptor) ocispec.Descriptor {
|
||||
return ocispec.Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Digest: d.Digest,
|
||||
Size: d.Size_,
|
||||
}
|
||||
}
|
||||
|
||||
func toMounts(apim []*mounttypes.Mount) []containerd.Mount {
|
||||
mounts := make([]containerd.Mount, len(apim))
|
||||
for i, m := range apim {
|
||||
mounts[i] = containerd.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package rootfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
||||
containerd_v1_types "github.com/containerd/containerd/api/types/descriptor"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func NewUnpackerFromClient(client rootfsapi.RootFSClient) rootfs.Unpacker {
|
||||
return remoteUnpacker{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type remoteUnpacker struct {
|
||||
client rootfsapi.RootFSClient
|
||||
}
|
||||
|
||||
func (rp remoteUnpacker) Unpack(ctx context.Context, layers []ocispec.Descriptor) (digest.Digest, error) {
|
||||
pr := rootfsapi.UnpackRequest{
|
||||
Layers: make([]*containerd_v1_types.Descriptor, len(layers)),
|
||||
}
|
||||
for i, l := range layers {
|
||||
pr.Layers[i] = &containerd_v1_types.Descriptor{
|
||||
MediaType: l.MediaType,
|
||||
Digest: l.Digest,
|
||||
Size_: l.Size,
|
||||
}
|
||||
}
|
||||
resp, err := rp.client.Unpack(ctx, &pr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.ChainID, nil
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package rootfs
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd"
|
||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
||||
containerd_v1_types "github.com/containerd/containerd/api/types/mount"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
"github.com/containerd/containerd/snapshot"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register("rootfs-grpc", &plugin.Registration{
|
||||
Type: plugin.GRPCPlugin,
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return NewService(ic.Content, ic.Snapshotter)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
store content.Store
|
||||
snapshotter snapshot.Snapshotter
|
||||
}
|
||||
|
||||
func NewService(store content.Store, snapshotter snapshot.Snapshotter) (*Service, error) {
|
||||
return &Service{
|
||||
store: store,
|
||||
snapshotter: snapshotter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Register(gs *grpc.Server) error {
|
||||
rootfsapi.RegisterRootFSServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Unpack(ctx context.Context, pr *rootfsapi.UnpackRequest) (*rootfsapi.UnpackResponse, error) {
|
||||
layers := make([]ocispec.Descriptor, len(pr.Layers))
|
||||
for i, l := range pr.Layers {
|
||||
layers[i] = ocispec.Descriptor{
|
||||
MediaType: l.MediaType,
|
||||
Digest: l.Digest,
|
||||
Size: l.Size_,
|
||||
}
|
||||
}
|
||||
log.G(ctx).Infof("Preparing %#v", layers)
|
||||
chainID, err := rootfs.Prepare(ctx, s.snapshotter, mounter{}, layers, s.store.Reader, emptyResolver, noopRegister)
|
||||
if err != nil {
|
||||
log.G(ctx).Errorf("Rootfs Prepare failed!: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
log.G(ctx).Infof("ChainID %#v", chainID)
|
||||
return &rootfsapi.UnpackResponse{
|
||||
ChainID: chainID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Prepare(ctx context.Context, ir *rootfsapi.PrepareRequest) (*rootfsapi.MountResponse, error) {
|
||||
mounts, err := rootfs.InitRootFS(ctx, ir.Name, ir.ChainID, ir.Readonly, s.snapshotter, mounter{})
|
||||
if err != nil {
|
||||
return nil, grpc.Errorf(codes.AlreadyExists, "%v", err)
|
||||
}
|
||||
return &rootfsapi.MountResponse{
|
||||
Mounts: apiMounts(mounts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Mounts(ctx context.Context, mr *rootfsapi.MountsRequest) (*rootfsapi.MountResponse, error) {
|
||||
mounts, err := s.snapshotter.Mounts(ctx, mr.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rootfsapi.MountResponse{
|
||||
Mounts: apiMounts(mounts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func apiMounts(mounts []containerd.Mount) []*containerd_v1_types.Mount {
|
||||
am := make([]*containerd_v1_types.Mount, len(mounts))
|
||||
for i, m := range mounts {
|
||||
am[i] = &containerd_v1_types.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
return am
|
||||
}
|
||||
|
||||
type mounter struct{}
|
||||
|
||||
func (mounter) Mount(dir string, mounts ...containerd.Mount) error {
|
||||
return containerd.MountAll(mounts, dir)
|
||||
}
|
||||
|
||||
func (mounter) Unmount(dir string) error {
|
||||
return containerd.Unmount(dir, 0)
|
||||
}
|
||||
|
||||
func emptyResolver(digest.Digest) digest.Digest {
|
||||
return digest.Digest("")
|
||||
}
|
||||
|
||||
func noopRegister(digest.Digest, digest.Digest) error {
|
||||
return nil
|
||||
}
|
||||
158
services/snapshot/client.go
Normal file
158
services/snapshot/client.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||
"github.com/containerd/containerd/snapshot"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// NewSnapshotterFromClient returns a new Snapshotter which communicates
|
||||
// over a GRPC connection.
|
||||
func NewSnapshotterFromClient(client snapshotapi.SnapshotClient) snapshot.Snapshotter {
|
||||
return &remoteSnapshotter{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type remoteSnapshotter struct {
|
||||
client snapshotapi.SnapshotClient
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Stat(ctx context.Context, key string) (snapshot.Info, error) {
|
||||
resp, err := r.client.Stat(ctx, &snapshotapi.StatRequest{Key: key})
|
||||
if err != nil {
|
||||
return snapshot.Info{}, rewriteGRPCError(err)
|
||||
}
|
||||
return toInfo(resp.Info), nil
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Usage(ctx context.Context, key string) (snapshot.Usage, error) {
|
||||
resp, err := r.client.Usage(ctx, &snapshotapi.UsageRequest{Key: key})
|
||||
if err != nil {
|
||||
return snapshot.Usage{}, rewriteGRPCError(err)
|
||||
}
|
||||
return toUsage(resp), nil
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Mounts(ctx context.Context, key string) ([]containerd.Mount, error) {
|
||||
resp, err := r.client.Mounts(ctx, &snapshotapi.MountsRequest{Key: key})
|
||||
if err != nil {
|
||||
return nil, rewriteGRPCError(err)
|
||||
}
|
||||
return toMounts(resp), nil
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Prepare(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
resp, err := r.client.Prepare(ctx, &snapshotapi.PrepareRequest{Key: key, Parent: parent})
|
||||
if err != nil {
|
||||
return nil, rewriteGRPCError(err)
|
||||
}
|
||||
return toMounts(resp), nil
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) View(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
resp, err := r.client.View(ctx, &snapshotapi.PrepareRequest{Key: key, Parent: parent})
|
||||
if err != nil {
|
||||
return nil, rewriteGRPCError(err)
|
||||
}
|
||||
return toMounts(resp), nil
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Commit(ctx context.Context, name, key string) error {
|
||||
_, err := r.client.Commit(ctx, &snapshotapi.CommitRequest{
|
||||
Name: name,
|
||||
Key: key,
|
||||
})
|
||||
return rewriteGRPCError(err)
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Remove(ctx context.Context, key string) error {
|
||||
_, err := r.client.Remove(ctx, &snapshotapi.RemoveRequest{Key: key})
|
||||
return rewriteGRPCError(err)
|
||||
}
|
||||
|
||||
func (r *remoteSnapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error {
|
||||
sc, err := r.client.List(ctx, &snapshotapi.ListRequest{})
|
||||
if err != nil {
|
||||
rewriteGRPCError(err)
|
||||
}
|
||||
for {
|
||||
resp, err := sc.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return rewriteGRPCError(err)
|
||||
}
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
for _, info := range resp.Info {
|
||||
if err := fn(ctx, toInfo(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteGRPCError(err error) error {
|
||||
switch grpc.Code(errors.Cause(err)) {
|
||||
case codes.AlreadyExists:
|
||||
return snapshot.ErrSnapshotExist
|
||||
case codes.NotFound:
|
||||
return snapshot.ErrSnapshotNotExist
|
||||
case codes.FailedPrecondition:
|
||||
desc := grpc.ErrorDesc(errors.Cause(err))
|
||||
if strings.Contains(desc, snapshot.ErrSnapshotNotActive.Error()) {
|
||||
return snapshot.ErrSnapshotNotActive
|
||||
}
|
||||
if strings.Contains(desc, snapshot.ErrSnapshotNotCommitted.Error()) {
|
||||
return snapshot.ErrSnapshotNotCommitted
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func toKind(kind snapshotapi.Kind) snapshot.Kind {
|
||||
if kind == snapshotapi.KindActive {
|
||||
return snapshot.KindActive
|
||||
}
|
||||
return snapshot.KindCommitted
|
||||
}
|
||||
|
||||
func toInfo(info snapshotapi.Info) snapshot.Info {
|
||||
return snapshot.Info{
|
||||
Name: info.Name,
|
||||
Parent: info.Parent,
|
||||
Kind: toKind(info.Kind),
|
||||
Readonly: info.Readonly,
|
||||
}
|
||||
}
|
||||
|
||||
func toUsage(resp *snapshotapi.UsageResponse) snapshot.Usage {
|
||||
return snapshot.Usage{
|
||||
Inodes: resp.Inodes,
|
||||
Size: resp.Size_,
|
||||
}
|
||||
}
|
||||
|
||||
func toMounts(resp *snapshotapi.MountsResponse) []containerd.Mount {
|
||||
mounts := make([]containerd.Mount, len(resp.Mounts))
|
||||
for i, m := range resp.Mounts {
|
||||
mounts[i] = containerd.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
204
services/snapshot/service.go
Normal file
204
services/snapshot/service.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||
mounttypes "github.com/containerd/containerd/api/types/mount"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/snapshot"
|
||||
protoempty "github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register("snapshots-grpc", &plugin.Registration{
|
||||
Type: plugin.GRPCPlugin,
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return newService(ic.Snapshotter)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var empty = &protoempty.Empty{}
|
||||
|
||||
type service struct {
|
||||
snapshotter snapshot.Snapshotter
|
||||
}
|
||||
|
||||
func newService(snapshotter snapshot.Snapshotter) (*service, error) {
|
||||
return &service{
|
||||
snapshotter: snapshotter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) Register(gs *grpc.Server) error {
|
||||
snapshotapi.RegisterSnapshotServer(gs, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Prepare(ctx context.Context, pr *snapshotapi.PrepareRequest) (*snapshotapi.MountsResponse, error) {
|
||||
log.G(ctx).WithField("parent", pr.Parent).WithField("key", pr.Key).Debugf("Preparing snapshot")
|
||||
// TODO: Apply namespace
|
||||
// TODO: Lookup snapshot id from metadata store
|
||||
mounts, err := s.snapshotter.Prepare(ctx, pr.Key, pr.Parent)
|
||||
if err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
return fromMounts(mounts), nil
|
||||
}
|
||||
|
||||
func (s *service) View(ctx context.Context, pr *snapshotapi.PrepareRequest) (*snapshotapi.MountsResponse, error) {
|
||||
log.G(ctx).WithField("parent", pr.Parent).WithField("key", pr.Key).Debugf("Preparing view snapshot")
|
||||
// TODO: Apply namespace
|
||||
// TODO: Lookup snapshot id from metadata store
|
||||
mounts, err := s.snapshotter.View(ctx, pr.Key, pr.Parent)
|
||||
if err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
return fromMounts(mounts), nil
|
||||
}
|
||||
|
||||
func (s *service) Mounts(ctx context.Context, mr *snapshotapi.MountsRequest) (*snapshotapi.MountsResponse, error) {
|
||||
log.G(ctx).WithField("key", mr.Key).Debugf("Getting snapshot mounts")
|
||||
// TODO: Apply namespace
|
||||
// TODO: Lookup snapshot id from metadata store
|
||||
mounts, err := s.snapshotter.Mounts(ctx, mr.Key)
|
||||
if err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
return fromMounts(mounts), nil
|
||||
}
|
||||
|
||||
func (s *service) Commit(ctx context.Context, cr *snapshotapi.CommitRequest) (*protoempty.Empty, error) {
|
||||
log.G(ctx).WithField("key", cr.Key).WithField("name", cr.Name).Debugf("Committing snapshot")
|
||||
// TODO: Apply namespace
|
||||
// TODO: Lookup snapshot id from metadata store
|
||||
if err := s.snapshotter.Commit(ctx, cr.Name, cr.Key); err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (s *service) Remove(ctx context.Context, rr *snapshotapi.RemoveRequest) (*protoempty.Empty, error) {
|
||||
log.G(ctx).WithField("key", rr.Key).Debugf("Removing snapshot")
|
||||
// TODO: Apply namespace
|
||||
// TODO: Lookup snapshot id from metadata store
|
||||
if err := s.snapshotter.Remove(ctx, rr.Key); err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
func (s *service) Stat(ctx context.Context, sr *snapshotapi.StatRequest) (*snapshotapi.StatResponse, error) {
|
||||
log.G(ctx).WithField("key", sr.Key).Debugf("Statting snapshot")
|
||||
// TODO: Apply namespace
|
||||
info, err := s.snapshotter.Stat(ctx, sr.Key)
|
||||
if err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
|
||||
return &snapshotapi.StatResponse{Info: fromInfo(info)}, nil
|
||||
}
|
||||
|
||||
func (s *service) List(sr *snapshotapi.ListRequest, ss snapshotapi.Snapshot_ListServer) error {
|
||||
// TODO: Apply namespace
|
||||
|
||||
var (
|
||||
buffer []snapshotapi.Info
|
||||
sendBlock = func(block []snapshotapi.Info) error {
|
||||
return ss.Send(&snapshotapi.ListResponse{
|
||||
Info: block,
|
||||
})
|
||||
}
|
||||
)
|
||||
err := s.snapshotter.Walk(ss.Context(), func(ctx gocontext.Context, info snapshot.Info) error {
|
||||
buffer = append(buffer, fromInfo(info))
|
||||
|
||||
if len(buffer) >= 100 {
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer = buffer[:0]
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buffer) > 0 {
|
||||
// Send remaining infos
|
||||
if err := sendBlock(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Usage(ctx context.Context, ur *snapshotapi.UsageRequest) (*snapshotapi.UsageResponse, error) {
|
||||
// TODO: Apply namespace
|
||||
usage, err := s.snapshotter.Usage(ctx, ur.Key)
|
||||
if err != nil {
|
||||
return nil, grpcError(err)
|
||||
}
|
||||
|
||||
return fromUsage(usage), nil
|
||||
}
|
||||
|
||||
func grpcError(err error) error {
|
||||
if snapshot.IsNotExist(err) {
|
||||
return grpc.Errorf(codes.NotFound, err.Error())
|
||||
}
|
||||
if snapshot.IsExist(err) {
|
||||
return grpc.Errorf(codes.AlreadyExists, err.Error())
|
||||
}
|
||||
if snapshot.IsNotActive(err) || snapshot.IsNotCommitted(err) {
|
||||
return grpc.Errorf(codes.FailedPrecondition, err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func fromKind(kind snapshot.Kind) snapshotapi.Kind {
|
||||
if kind == snapshot.KindActive {
|
||||
return snapshotapi.KindActive
|
||||
}
|
||||
return snapshotapi.KindCommitted
|
||||
}
|
||||
|
||||
func fromInfo(info snapshot.Info) snapshotapi.Info {
|
||||
return snapshotapi.Info{
|
||||
Name: info.Name,
|
||||
Parent: info.Parent,
|
||||
Kind: fromKind(info.Kind),
|
||||
Readonly: info.Readonly,
|
||||
}
|
||||
}
|
||||
|
||||
func fromUsage(usage snapshot.Usage) *snapshotapi.UsageResponse {
|
||||
return &snapshotapi.UsageResponse{
|
||||
Inodes: usage.Inodes,
|
||||
Size_: usage.Size,
|
||||
}
|
||||
}
|
||||
|
||||
func fromMounts(mounts []containerd.Mount) *snapshotapi.MountsResponse {
|
||||
resp := &snapshotapi.MountsResponse{
|
||||
Mounts: make([]*mounttypes.Mount, len(mounts)),
|
||||
}
|
||||
for i, m := range mounts {
|
||||
resp.Mounts[i] = &mounttypes.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
Reference in New Issue
Block a user