Merge pull request #849 from dmcgowan/snapshot-service
Add snapshot and diff service
This commit is contained in:
commit
3695ba77bb
1111
api/services/diff/diff.pb.go
Normal file
1111
api/services/diff/diff.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
58
api/services/diff/diff.proto
Normal file
58
api/services/diff/diff.proto
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package containerd.v1;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "github.com/containerd/containerd/api/types/mount/mount.proto";
|
||||||
|
import "github.com/containerd/containerd/api/types/descriptor/descriptor.proto";
|
||||||
|
|
||||||
|
// Diff service creates and applies diffs
|
||||||
|
service Diff {
|
||||||
|
// Apply applies the content associated with the provided digests onto
|
||||||
|
// the provided mounts. Archive content will be extracted and
|
||||||
|
// decompressed if necessary.
|
||||||
|
rpc Apply(ApplyRequest) returns (ApplyResponse);
|
||||||
|
|
||||||
|
// Diff creates a diff between the given mounts and uploads the result
|
||||||
|
// to the content store.
|
||||||
|
rpc Diff(DiffRequest) returns (DiffResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message ApplyRequest {
|
||||||
|
// Diff is the descriptor of the diff to be extracted
|
||||||
|
containerd.v1.types.Descriptor diff = 1;
|
||||||
|
|
||||||
|
repeated containerd.v1.types.Mount mounts = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ApplyResponse {
|
||||||
|
// Applied is the descriptor for the object which was applied.
|
||||||
|
// If the input was a compressed blob then the result will be
|
||||||
|
// the descriptor for the uncompressed blob.
|
||||||
|
containerd.v1.types.Descriptor applied = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiffRequest {
|
||||||
|
// Left are the mounts which represent the older copy
|
||||||
|
// in which is the base of the computed changes.
|
||||||
|
repeated containerd.v1.types.Mount left = 1;
|
||||||
|
|
||||||
|
// Right are the mounts which represents the newer copy
|
||||||
|
// in which changes from the left were made into.
|
||||||
|
repeated containerd.v1.types.Mount right = 2;
|
||||||
|
|
||||||
|
// MediaType is the media type descriptor for the created diff
|
||||||
|
// object
|
||||||
|
string media_type = 3;
|
||||||
|
|
||||||
|
// Ref identifies the pre-commit content store object. This
|
||||||
|
// reference can be used to get the status from the content store.
|
||||||
|
string ref = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiffResponse {
|
||||||
|
// Diff is the descriptor of the diff which can be applied
|
||||||
|
containerd.v1.types.Descriptor diff = 3;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package containerd.v1;
|
|
||||||
|
|
||||||
import "gogoproto/gogo.proto";
|
|
||||||
import "github.com/containerd/containerd/api/types/mount/mount.proto";
|
|
||||||
import "github.com/containerd/containerd/api/types/descriptor/descriptor.proto";
|
|
||||||
|
|
||||||
service RootFS {
|
|
||||||
rpc Unpack(UnpackRequest) returns (UnpackResponse);
|
|
||||||
rpc Prepare(PrepareRequest) returns (MountResponse);
|
|
||||||
rpc Mounts(MountsRequest) returns (MountResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnpackRequest {
|
|
||||||
repeated containerd.v1.types.Descriptor layers = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnpackResponse {
|
|
||||||
string chainid = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false, (gogoproto.customname) = "ChainID"];
|
|
||||||
}
|
|
||||||
|
|
||||||
message PrepareRequest {
|
|
||||||
string name = 1;
|
|
||||||
string chain_id = 2 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false, (gogoproto.customname) = "ChainID"];
|
|
||||||
bool readonly = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message MountsRequest {
|
|
||||||
string name = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message MountResponse {
|
|
||||||
repeated containerd.v1.types.Mount mounts = 1;
|
|
||||||
}
|
|
2379
api/services/snapshot/snapshots.pb.go
Normal file
2379
api/services/snapshot/snapshots.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
81
api/services/snapshot/snapshots.proto
Normal file
81
api/services/snapshot/snapshots.proto
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package containerd.v1.snapshot;
|
||||||
|
|
||||||
|
import "gogoproto/gogo.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "github.com/containerd/containerd/api/types/mount/mount.proto";
|
||||||
|
|
||||||
|
// Snapshot service manages snapshots
|
||||||
|
service Snapshot {
|
||||||
|
rpc Prepare(PrepareRequest) returns (MountsResponse);
|
||||||
|
rpc View(PrepareRequest) returns (MountsResponse);
|
||||||
|
rpc Mounts(MountsRequest) returns (MountsResponse);
|
||||||
|
rpc Commit(CommitRequest) returns (google.protobuf.Empty);
|
||||||
|
rpc Remove(RemoveRequest) returns (google.protobuf.Empty);
|
||||||
|
rpc Stat(StatRequest) returns (StatResponse);
|
||||||
|
rpc List(ListRequest) returns (stream ListResponse);
|
||||||
|
rpc Usage(UsageRequest) returns (UsageResponse);
|
||||||
|
// "Snapshot" prepares a new set of mounts from existing name
|
||||||
|
}
|
||||||
|
|
||||||
|
message PrepareRequest {
|
||||||
|
string key = 1;
|
||||||
|
string parent = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MountsRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MountsResponse {
|
||||||
|
repeated containerd.v1.types.Mount mounts = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CommitRequest {
|
||||||
|
string name = 1;
|
||||||
|
string key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Kind {
|
||||||
|
option (gogoproto.goproto_enum_prefix) = false;
|
||||||
|
option (gogoproto.enum_customname) = "Kind";
|
||||||
|
|
||||||
|
ACTIVE = 0 [(gogoproto.enumvalue_customname) = "KindActive"];
|
||||||
|
|
||||||
|
COMMITTED = 1 [(gogoproto.enumvalue_customname) = "KindCommitted"];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Info {
|
||||||
|
string name = 1;
|
||||||
|
string parent = 2;
|
||||||
|
Kind kind = 3;
|
||||||
|
bool readonly = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatResponse {
|
||||||
|
Info info = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListRequest{}
|
||||||
|
|
||||||
|
message ListResponse {
|
||||||
|
repeated Info info = 1 [(gogoproto.nullable) = false];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UsageRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UsageResponse {
|
||||||
|
int64 inodes = 2;
|
||||||
|
int64 size = 1;
|
||||||
|
}
|
@ -3,10 +3,11 @@ package main
|
|||||||
// register containerd builtins here
|
// register containerd builtins here
|
||||||
import (
|
import (
|
||||||
_ "github.com/containerd/containerd/services/content"
|
_ "github.com/containerd/containerd/services/content"
|
||||||
|
_ "github.com/containerd/containerd/services/diff"
|
||||||
_ "github.com/containerd/containerd/services/execution"
|
_ "github.com/containerd/containerd/services/execution"
|
||||||
_ "github.com/containerd/containerd/services/healthcheck"
|
_ "github.com/containerd/containerd/services/healthcheck"
|
||||||
_ "github.com/containerd/containerd/services/images"
|
_ "github.com/containerd/containerd/services/images"
|
||||||
_ "github.com/containerd/containerd/services/metrics"
|
_ "github.com/containerd/containerd/services/metrics"
|
||||||
_ "github.com/containerd/containerd/services/rootfs"
|
_ "github.com/containerd/containerd/services/snapshot"
|
||||||
_ "github.com/containerd/containerd/services/version"
|
_ "github.com/containerd/containerd/services/version"
|
||||||
)
|
)
|
||||||
|
@ -18,9 +18,10 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
contentapi "github.com/containerd/containerd/api/services/content"
|
contentapi "github.com/containerd/containerd/api/services/content"
|
||||||
|
diffapi "github.com/containerd/containerd/api/services/diff"
|
||||||
api "github.com/containerd/containerd/api/services/execution"
|
api "github.com/containerd/containerd/api/services/execution"
|
||||||
imagesapi "github.com/containerd/containerd/api/services/images"
|
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||||
versionapi "github.com/containerd/containerd/api/services/version"
|
versionapi "github.com/containerd/containerd/api/services/version"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
@ -417,14 +418,16 @@ func interceptor(ctx gocontext.Context,
|
|||||||
ctx = log.WithModule(ctx, "execution")
|
ctx = log.WithModule(ctx, "execution")
|
||||||
case contentapi.ContentServer:
|
case contentapi.ContentServer:
|
||||||
ctx = log.WithModule(ctx, "content")
|
ctx = log.WithModule(ctx, "content")
|
||||||
case rootfsapi.RootFSServer:
|
|
||||||
ctx = log.WithModule(ctx, "rootfs")
|
|
||||||
case imagesapi.ImagesServer:
|
case imagesapi.ImagesServer:
|
||||||
ctx = log.WithModule(ctx, "images")
|
ctx = log.WithModule(ctx, "images")
|
||||||
case grpc_health_v1.HealthServer:
|
case grpc_health_v1.HealthServer:
|
||||||
// No need to change the context
|
// No need to change the context
|
||||||
case versionapi.VersionServer:
|
case versionapi.VersionServer:
|
||||||
ctx = log.WithModule(ctx, "version")
|
ctx = log.WithModule(ctx, "version")
|
||||||
|
case snapshotapi.SnapshotServer:
|
||||||
|
ctx = log.WithModule(ctx, "snapshot")
|
||||||
|
case diffapi.DiffServer:
|
||||||
|
ctx = log.WithModule(ctx, "diff")
|
||||||
default:
|
default:
|
||||||
log.G(ctx).Warnf("unknown GRPC server type: %#v\n", info.Server)
|
log.G(ctx).Warnf("unknown GRPC server type: %#v\n", info.Server)
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
mounttypes "github.com/containerd/containerd/api/types/mount"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/snapshot"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -59,7 +58,7 @@ var runCommand = cli.Command{
|
|||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
resp *rootfsapi.MountResponse
|
mounts []containerd.Mount
|
||||||
imageConfig ocispec.Image
|
imageConfig ocispec.Image
|
||||||
|
|
||||||
ctx = gocontext.Background()
|
ctx = gocontext.Background()
|
||||||
@ -87,7 +86,7 @@ var runCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootfsClient, err := getRootFSService(context)
|
snapshotter, err := getSnapshotter(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -111,21 +110,16 @@ var runCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := rootfsClient.Prepare(gocontext.TODO(), &rootfsapi.PrepareRequest{
|
mounts, err = snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String())
|
||||||
Name: id,
|
if err != nil {
|
||||||
ChainID: identity.ChainID(diffIDs),
|
if !snapshot.IsExist(err) {
|
||||||
}); err != nil {
|
|
||||||
if grpc.Code(err) != codes.AlreadyExists {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
mounts, err = snapshotter.Mounts(ctx, id)
|
||||||
|
|
||||||
resp, err = rootfsClient.Mounts(gocontext.TODO(), &rootfsapi.MountsRequest{
|
|
||||||
Name: id,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ic, err := image.Config(ctx, content)
|
ic, err := image.Config(ctx, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -153,8 +147,13 @@ var runCommand = cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp != nil {
|
for _, m := range mounts {
|
||||||
create.Rootfs = resp.Mounts
|
create.Rootfs = append(create.Rootfs, &mounttypes.Mount{
|
||||||
|
Type: m.Type,
|
||||||
|
Source: m.Source,
|
||||||
|
Options: m.Options,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
var con console.Console
|
var con console.Console
|
||||||
if create.Terminal {
|
if create.Terminal {
|
||||||
|
@ -15,13 +15,15 @@ import (
|
|||||||
contentapi "github.com/containerd/containerd/api/services/content"
|
contentapi "github.com/containerd/containerd/api/services/content"
|
||||||
"github.com/containerd/containerd/api/services/execution"
|
"github.com/containerd/containerd/api/services/execution"
|
||||||
imagesapi "github.com/containerd/containerd/api/services/images"
|
imagesapi "github.com/containerd/containerd/api/services/images"
|
||||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||||
versionservice "github.com/containerd/containerd/api/services/version"
|
versionservice "github.com/containerd/containerd/api/services/version"
|
||||||
"github.com/containerd/containerd/api/types/container"
|
"github.com/containerd/containerd/api/types/container"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
contentservice "github.com/containerd/containerd/services/content"
|
contentservice "github.com/containerd/containerd/services/content"
|
||||||
imagesservice "github.com/containerd/containerd/services/images"
|
imagesservice "github.com/containerd/containerd/services/images"
|
||||||
|
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
||||||
|
"github.com/containerd/containerd/snapshot"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
@ -44,12 +46,12 @@ func getContentStore(context *cli.Context) (content.Store, error) {
|
|||||||
return contentservice.NewStoreFromClient(contentapi.NewContentClient(conn)), nil
|
return contentservice.NewStoreFromClient(contentapi.NewContentClient(conn)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRootFSService(context *cli.Context) (rootfsapi.RootFSClient, error) {
|
func getSnapshotter(context *cli.Context) (snapshot.Snapshotter, error) {
|
||||||
conn, err := getGRPCConnection(context)
|
conn, err := getGRPCConnection(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rootfsapi.NewRootFSClient(conn), nil
|
return snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageStore(clicontext *cli.Context) (images.Store, error) {
|
func getImageStore(clicontext *cli.Context) (images.Store, error) {
|
||||||
|
36
cmd/dist/common.go
vendored
36
cmd/dist/common.go
vendored
@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -18,8 +19,10 @@ import (
|
|||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
"github.com/containerd/containerd/remotes/docker"
|
"github.com/containerd/containerd/remotes/docker"
|
||||||
|
"github.com/containerd/containerd/rootfs"
|
||||||
contentservice "github.com/containerd/containerd/services/content"
|
contentservice "github.com/containerd/containerd/services/content"
|
||||||
imagesservice "github.com/containerd/containerd/services/images"
|
imagesservice "github.com/containerd/containerd/services/images"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -151,3 +154,36 @@ func passwordPrompt() (string, error) {
|
|||||||
}
|
}
|
||||||
return string(line), nil
|
return string(line), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getImageLayers(ctx context.Context, image images.Image, cs content.Store) ([]rootfs.Layer, error) {
|
||||||
|
p, err := content.ReadBlob(ctx, cs, image.Target.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to read manifest blob")
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest ocispec.Manifest
|
||||||
|
if err := json.Unmarshal(p, &manifest); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
diffIDs, err := image.RootFS(ctx, cs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to resolve rootfs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(diffIDs) != len(manifest.Layers) {
|
||||||
|
return nil, errors.Errorf("mismatched image rootfs and manifest layers")
|
||||||
|
}
|
||||||
|
|
||||||
|
layers := make([]rootfs.Layer, len(diffIDs))
|
||||||
|
for i := range diffIDs {
|
||||||
|
layers[i].Diff = ocispec.Descriptor{
|
||||||
|
// TODO: derive media type from compressed type
|
||||||
|
MediaType: ocispec.MediaTypeImageLayer,
|
||||||
|
Digest: diffIDs[i],
|
||||||
|
}
|
||||||
|
layers[i].Blob = manifest.Layers[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
38
cmd/dist/pull.go
vendored
38
cmd/dist/pull.go
vendored
@ -2,19 +2,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
diffapi "github.com/containerd/containerd/api/services/diff"
|
||||||
"github.com/containerd/containerd/content"
|
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/progress"
|
"github.com/containerd/containerd/progress"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
rootfsservice "github.com/containerd/containerd/services/rootfs"
|
"github.com/containerd/containerd/rootfs"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
diffservice "github.com/containerd/containerd/services/diff"
|
||||||
|
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@ -101,43 +101,31 @@ command. As part of this process, we do the following:
|
|||||||
// TODO(stevvooe): This section unpacks the layers and resolves the
|
// TODO(stevvooe): This section unpacks the layers and resolves the
|
||||||
// root filesystem chainid for the image. For now, we just print
|
// root filesystem chainid for the image. For now, we just print
|
||||||
// it, but we should keep track of this in the metadata storage.
|
// it, but we should keep track of this in the metadata storage.
|
||||||
|
|
||||||
image, err := imageStore.Get(ctx, resolvedImageName)
|
image, err := imageStore.Get(ctx, resolvedImageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.G(ctx).Fatal(err)
|
log.G(ctx).WithError(err).Fatal("Failed to get image")
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := content.ReadBlob(ctx, cs, image.Target.Digest)
|
layers, err := getImageLayers(ctx, image, cs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.G(ctx).Fatal(err)
|
log.G(ctx).WithError(err).Fatal("Failed to get rootfs layers")
|
||||||
}
|
|
||||||
|
|
||||||
var manifest ocispec.Manifest
|
|
||||||
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
||||||
log.G(ctx).Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := connectGRPC(clicontext)
|
conn, err := connectGRPC(clicontext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.G(ctx).Fatal(err)
|
log.G(ctx).Fatal(err)
|
||||||
}
|
}
|
||||||
rootfs := rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn))
|
snapshotter := snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn))
|
||||||
|
applier := diffservice.NewApplierFromClient(diffapi.NewDiffClient(conn))
|
||||||
|
|
||||||
log.G(ctx).Info("unpacking rootfs")
|
log.G(ctx).Info("unpacking rootfs")
|
||||||
chainID, err := rootfs.Unpack(ctx, manifest.Layers)
|
|
||||||
|
chainID, err := rootfs.ApplyLayers(ctx, layers, snapshotter, applier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.G(ctx).Fatal(err)
|
log.G(ctx).Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diffIDs, err := image.RootFS(ctx, cs)
|
log.G(ctx).Infof("Unpacked chain id: %s", chainID)
|
||||||
if err != nil {
|
|
||||||
log.G(ctx).WithError(err).Fatal("failed resolving rootfs")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedChainID := identity.ChainID(diffIDs)
|
|
||||||
if expectedChainID != chainID {
|
|
||||||
log.G(ctx).Fatal("rootfs service did not match chainid")
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
78
cmd/dist/rootfs.go
vendored
78
cmd/dist/rootfs.go
vendored
@ -1,17 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
|
diffapi "github.com/containerd/containerd/api/services/diff"
|
||||||
"github.com/containerd/containerd/content"
|
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
rootfsservice "github.com/containerd/containerd/services/rootfs"
|
"github.com/containerd/containerd/rootfs"
|
||||||
|
diffservice "github.com/containerd/containerd/services/diff"
|
||||||
|
snapshotservice "github.com/containerd/containerd/services/snapshot"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@ -42,23 +42,32 @@ var rootfsUnpackCommand = cli.Command{
|
|||||||
|
|
||||||
log.G(ctx).Infof("unpacking layers from manifest %s", dgst.String())
|
log.G(ctx).Infof("unpacking layers from manifest %s", dgst.String())
|
||||||
|
|
||||||
conn, err := connectGRPC(clicontext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cs, err := resolveContentStore(clicontext)
|
cs, err := resolveContentStore(clicontext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := resolveManifest(ctx, cs, dgst)
|
image := images.Image{
|
||||||
if err != nil {
|
Target: ocispec.Descriptor{
|
||||||
return err
|
MediaType: ocispec.MediaTypeImageManifest,
|
||||||
|
Digest: dgst,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unpacker := rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn))
|
layers, err := getImageLayers(ctx, image, cs)
|
||||||
chainID, err := unpacker.Unpack(ctx, m.Layers)
|
if err != nil {
|
||||||
|
log.G(ctx).WithError(err).Fatal("Failed to get rootfs layers")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := connectGRPC(clicontext)
|
||||||
|
if err != nil {
|
||||||
|
log.G(ctx).Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotter := snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn))
|
||||||
|
applier := diffservice.NewApplierFromClient(diffapi.NewDiffClient(conn))
|
||||||
|
|
||||||
|
chainID, err := rootfs.ApplyLayers(ctx, layers, snapshotter, applier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -95,48 +104,17 @@ var rootfsPrepareCommand = cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rclient := rootfsapi.NewRootFSClient(conn)
|
snapshotter := snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn))
|
||||||
|
|
||||||
ir := &rootfsapi.PrepareRequest{
|
mounts, err := snapshotter.Prepare(ctx, target, dgst.String())
|
||||||
Name: target,
|
|
||||||
ChainID: dgst,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := rclient.Prepare(ctx, ir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, m := range resp.Mounts {
|
for _, m := range mounts {
|
||||||
fmt.Fprintf(os.Stdout, "mount -t %s %s %s -o %s\n", m.Type, m.Source, target, strings.Join(m.Options, ","))
|
fmt.Fprintf(os.Stdout, "mount -t %s %s %s -o %s\n", m.Type, m.Source, target, strings.Join(m.Options, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveManifest(ctx context.Context, provider content.Provider, dgst digest.Digest) (ocispec.Manifest, error) {
|
|
||||||
p, err := readAll(ctx, provider, dgst)
|
|
||||||
if err != nil {
|
|
||||||
return ocispec.Manifest{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(stevvooe): This assumption that we get a manifest is unfortunate.
|
|
||||||
// Need to provide way to resolve what the type of the target is.
|
|
||||||
var manifest ocispec.Manifest
|
|
||||||
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
||||||
return ocispec.Manifest{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return manifest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll(ctx context.Context, provider content.Provider, dgst digest.Digest) ([]byte, error) {
|
|
||||||
rc, err := provider.Reader(ctx, dgst)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
return ioutil.ReadAll(rc)
|
|
||||||
}
|
|
||||||
|
160
rootfs/apply.go
160
rootfs/apply.go
@ -2,13 +2,9 @@ package rootfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/archive"
|
|
||||||
"github.com/containerd/containerd/archive/compression"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/snapshot"
|
"github.com/containerd/containerd/snapshot"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
@ -17,134 +13,72 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Unpacker interface {
|
type Applier interface {
|
||||||
Unpack(ctx context.Context, layers []ocispec.Descriptor) (digest.Digest, error)
|
Apply(context.Context, ocispec.Descriptor, []containerd.Mount) (ocispec.Descriptor, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mounter interface {
|
type Layer struct {
|
||||||
Mount(target string, mounts ...containerd.Mount) error
|
Diff ocispec.Descriptor
|
||||||
Unmount(target string) error
|
Blob ocispec.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyLayer applies the layer to the provided parent. The resulting snapshot
|
func ApplyLayers(ctx context.Context, layers []Layer, sn snapshot.Snapshotter, a Applier) (digest.Digest, error) {
|
||||||
// will be stored under its ChainID.
|
var chain []digest.Digest
|
||||||
//
|
for _, layer := range layers {
|
||||||
// The parent *must* be the chainID of the parent layer.
|
if err := applyLayer(ctx, layer, chain, sn, a); err != nil {
|
||||||
//
|
// TODO: possibly wait and retry if extraction of same chain id was in progress
|
||||||
// The returned digest is the diffID for the applied layer.
|
|
||||||
func ApplyLayer(snapshots snapshot.Snapshotter, mounter Mounter, rd io.Reader, parent digest.Digest) (digest.Digest, error) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
// create a temporary directory to work from, needs to be on same
|
|
||||||
// filesystem. Probably better if this shared but we'll use a tempdir, for
|
|
||||||
// now.
|
|
||||||
dir, err := ioutil.TempDir("", "unpack-")
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrapf(err, "creating temporary directory failed")
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// TODO(stevvooe): Choose this key WAY more carefully. We should be able to
|
|
||||||
// create collisions for concurrent, conflicting unpack processes but we
|
|
||||||
// would need to have it be a function of the parent diffID and child
|
|
||||||
// layerID (since we don't know the diffID until we are done!).
|
|
||||||
key := dir
|
|
||||||
|
|
||||||
mounts, err := snapshots.Prepare(ctx, key, parent.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mounter.Mount(dir, mounts...); err != nil {
|
chain = append(chain, layer.Diff.Digest)
|
||||||
if err := snapshots.Remove(ctx, key); err != nil {
|
|
||||||
log.L.WithError(err).Error("snapshot rollback failed")
|
|
||||||
}
|
}
|
||||||
return "", err
|
return identity.ChainID(chain), nil
|
||||||
}
|
|
||||||
defer mounter.Unmount(dir)
|
|
||||||
|
|
||||||
rd, err = compression.DecompressStream(rd)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
digester := digest.Canonical.Digester() // used to calculate diffID.
|
func applyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshot.Snapshotter, a Applier) error {
|
||||||
rd = io.TeeReader(rd, digester.Hash())
|
|
||||||
|
|
||||||
if _, err := archive.Apply(context.Background(), dir, rd); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
diffID := digester.Digest()
|
|
||||||
|
|
||||||
chainID := diffID
|
|
||||||
if parent != "" {
|
|
||||||
chainID = identity.ChainID([]digest.Digest{parent, chainID})
|
|
||||||
}
|
|
||||||
if _, err := snapshots.Stat(ctx, chainID.String()); err == nil {
|
|
||||||
return diffID, snapshots.Remove(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffID, snapshots.Commit(ctx, chainID.String(), key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the root filesystem from the set of layers. Snapshots are created
|
|
||||||
// for each layer if they don't exist, keyed by their chain id. If the snapshot
|
|
||||||
// already exists, it will be skipped.
|
|
||||||
//
|
|
||||||
// If successful, the chainID for the top-level layer is returned. That
|
|
||||||
// identifier can be used to check out a snapshot.
|
|
||||||
func Prepare(ctx context.Context, snapshots snapshot.Snapshotter, mounter Mounter, layers []ocispec.Descriptor,
|
|
||||||
// TODO(stevvooe): The following functions are candidate for internal
|
|
||||||
// object functions. We can use these to formulate the beginnings of a
|
|
||||||
// rootfs Controller.
|
|
||||||
//
|
|
||||||
// Just pass them in for now.
|
|
||||||
openBlob func(context.Context, digest.Digest) (io.ReadCloser, error),
|
|
||||||
resolveDiffID func(digest.Digest) digest.Digest,
|
|
||||||
registerDiffID func(diffID, dgst digest.Digest) error) (digest.Digest, error) {
|
|
||||||
var (
|
var (
|
||||||
parent digest.Digest
|
parent = identity.ChainID(chain)
|
||||||
chain []digest.Digest
|
chainID = identity.ChainID(append(chain, layer.Diff.Digest))
|
||||||
|
diff ocispec.Descriptor
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, layer := range layers {
|
_, err := sn.Stat(ctx, chainID.String())
|
||||||
// This will convert a possibly compressed layer hash to the
|
if err == nil {
|
||||||
// uncompressed hash, if we know about it. If we don't, we unpack and
|
log.G(ctx).Debugf("Extraction not needed, layer snapshot exists")
|
||||||
// calculate it. If we do have it, we then calculate the chain id for
|
return nil
|
||||||
// the application and see if the snapshot is there.
|
} else if !snapshot.IsNotExist(err) {
|
||||||
diffID := resolveDiffID(layer.Digest)
|
return errors.Wrap(err, "failed to stat snapshot")
|
||||||
if diffID != "" {
|
|
||||||
chainLocal := append(chain, diffID)
|
|
||||||
chainID := identity.ChainID(chainLocal)
|
|
||||||
|
|
||||||
if _, err := snapshots.Stat(ctx, chainID.String()); err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := openBlob(ctx, layer.Digest)
|
key := fmt.Sprintf("extract %s", chainID)
|
||||||
|
|
||||||
|
// Prepare snapshot with from parent
|
||||||
|
mounts, err := sn.Prepare(ctx, key, parent.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
//TODO: If is snapshot exists error, retry
|
||||||
|
return errors.Wrap(err, "failed to prepare extraction layer")
|
||||||
}
|
}
|
||||||
defer rc.Close() // pretty lazy!
|
defer func() {
|
||||||
|
|
||||||
diffID, err = ApplyLayer(snapshots, mounter, rc, parent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
log.G(ctx).WithError(err).WithField("key", key).Infof("Apply failure, attempting cleanup")
|
||||||
|
if rerr := sn.Remove(ctx, key); rerr != nil {
|
||||||
|
log.G(ctx).WithError(rerr).Warnf("Extraction snapshot %q removal failed: %v", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
diff, err = a.Apply(ctx, layer.Blob, mounts)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to extract layer %s", layer.Diff.Digest)
|
||||||
|
}
|
||||||
|
if diff.Digest != layer.Diff.Digest {
|
||||||
|
err = errors.Errorf("wrong diff id calculated on extraction %q", diff.Digest)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the association between the diffID and the layer's digest.
|
if err = sn.Commit(ctx, chainID.String(), key); err != nil {
|
||||||
// For uncompressed layers, this will be the same. For compressed
|
return errors.Wrapf(err, "failed to commit snapshot %s", parent)
|
||||||
// layers, we can look up the diffID from the digest if we've already
|
|
||||||
// unpacked it.
|
|
||||||
if err := registerDiffID(diffID, layer.Digest); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chain = append(chain, diffID)
|
return nil
|
||||||
parent = identity.ChainID(chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent, nil
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ var (
|
|||||||
|
|
||||||
type initializerFunc func(string) error
|
type initializerFunc func(string) error
|
||||||
|
|
||||||
|
type Mounter interface {
|
||||||
|
Mount(target string, mounts ...containerd.Mount) error
|
||||||
|
Unmount(target string) error
|
||||||
|
}
|
||||||
|
|
||||||
func InitRootFS(ctx context.Context, name string, parent digest.Digest, readonly bool, snapshotter snapshot.Snapshotter, mounter Mounter) ([]containerd.Mount, error) {
|
func InitRootFS(ctx context.Context, name string, parent digest.Digest, readonly bool, snapshotter snapshot.Snapshotter, mounter Mounter) ([]containerd.Mount, error) {
|
||||||
_, err := snapshotter.Stat(ctx, name)
|
_, err := snapshotter.Stat(ctx, name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
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
|
||||||
|
}
|
@ -63,7 +63,7 @@ func (Kind) EnumDescriptor() ([]byte, []int) { return fileDescriptorRecord, []in
|
|||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
ID uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
ID uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
Parent string `protobuf:"bytes,2,opt,name=parent,proto3" json:"parent,omitempty"`
|
Parent string `protobuf:"bytes,2,opt,name=parent,proto3" json:"parent,omitempty"`
|
||||||
Kind Kind `protobuf:"varint,4,opt,name=kind,proto3,enum=containerd.v1.Kind" json:"kind,omitempty"`
|
Kind Kind `protobuf:"varint,4,opt,name=kind,proto3,enum=containerd.snapshot.v1.Kind" json:"kind,omitempty"`
|
||||||
Readonly bool `protobuf:"varint,5,opt,name=readonly,proto3" json:"readonly,omitempty"`
|
Readonly bool `protobuf:"varint,5,opt,name=readonly,proto3" json:"readonly,omitempty"`
|
||||||
// inodes stores the number inodes in use for the snapshot.
|
// inodes stores the number inodes in use for the snapshot.
|
||||||
//
|
//
|
||||||
@ -81,8 +81,8 @@ func (*Snapshot) ProtoMessage() {}
|
|||||||
func (*Snapshot) Descriptor() ([]byte, []int) { return fileDescriptorRecord, []int{0} }
|
func (*Snapshot) Descriptor() ([]byte, []int) { return fileDescriptorRecord, []int{0} }
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto1.RegisterType((*Snapshot)(nil), "containerd.v1.Snapshot")
|
proto1.RegisterType((*Snapshot)(nil), "containerd.snapshot.v1.Snapshot")
|
||||||
proto1.RegisterEnum("containerd.v1.Kind", Kind_name, Kind_value)
|
proto1.RegisterEnum("containerd.snapshot.v1.Kind", Kind_name, Kind_value)
|
||||||
}
|
}
|
||||||
func (m *Snapshot) Marshal() (dAtA []byte, err error) {
|
func (m *Snapshot) Marshal() (dAtA []byte, err error) {
|
||||||
size := m.Size()
|
size := m.Size()
|
||||||
@ -511,26 +511,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptorRecord = []byte{
|
var fileDescriptorRecord = []byte{
|
||||||
// 330 bytes of a gzipped FileDescriptorProto
|
// 334 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xbd, 0x4e, 0xf3, 0x30,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xbb, 0x4e, 0xf3, 0x30,
|
||||||
0x18, 0x85, 0xe3, 0x7c, 0xf9, 0x42, 0x6b, 0xd1, 0xaa, 0x18, 0x54, 0x59, 0x19, 0x8c, 0xc5, 0x42,
|
0x1c, 0xc5, 0xe3, 0x7c, 0xf9, 0x42, 0x6b, 0x41, 0x55, 0x2c, 0x54, 0x59, 0x11, 0x32, 0x16, 0x53,
|
||||||
0xc4, 0x90, 0x08, 0xb8, 0x82, 0xfe, 0x0d, 0x55, 0x55, 0x21, 0x85, 0x8a, 0x3d, 0x8d, 0xad, 0xd4,
|
0xc4, 0x90, 0x70, 0x79, 0x82, 0xde, 0x86, 0xaa, 0xaa, 0x90, 0x42, 0xc5, 0x9e, 0xc6, 0x56, 0x6a,
|
||||||
0x82, 0xda, 0x95, 0x63, 0x2a, 0xc1, 0xc4, 0x88, 0x7a, 0x0f, 0x9d, 0x60, 0xe3, 0x0e, 0xb8, 0x82,
|
0x41, 0xed, 0xca, 0x31, 0x95, 0x60, 0x62, 0x44, 0x7d, 0x87, 0x4e, 0xf0, 0x08, 0x4c, 0x3c, 0x41,
|
||||||
0x8e, 0x8c, 0x4c, 0x88, 0xe6, 0x4a, 0x50, 0xd2, 0x8a, 0x9f, 0xed, 0x9c, 0xe3, 0x47, 0x8f, 0xa5,
|
0x47, 0x46, 0x26, 0x44, 0xf3, 0x24, 0x28, 0x69, 0xb9, 0x0c, 0x6c, 0xe7, 0xf7, 0xf7, 0x4f, 0xe7,
|
||||||
0x17, 0x76, 0x53, 0x61, 0x26, 0xb7, 0xe3, 0x20, 0x51, 0xd3, 0x30, 0x51, 0xd2, 0xc4, 0x42, 0x72,
|
0x48, 0x86, 0x9d, 0x54, 0x98, 0xf1, 0xcd, 0x28, 0x48, 0xd4, 0x24, 0x4c, 0x94, 0x34, 0xb1, 0x90,
|
||||||
0xcd, 0x7e, 0xc7, 0x4c, 0xc6, 0xb3, 0x6c, 0xa2, 0x4c, 0x98, 0x19, 0xa5, 0xe3, 0x94, 0x87, 0x33,
|
0x5c, 0xb3, 0xdf, 0x31, 0x93, 0xf1, 0x34, 0x1b, 0x2b, 0x13, 0x66, 0x46, 0xe9, 0x38, 0xe5, 0xe1,
|
||||||
0xad, 0x8c, 0x0a, 0x35, 0x4f, 0x94, 0x66, 0x41, 0x59, 0x50, 0xed, 0x87, 0x0f, 0xe6, 0xa7, 0xde,
|
0x54, 0x2b, 0xa3, 0x42, 0xcd, 0x13, 0xa5, 0x59, 0x50, 0x02, 0x6a, 0xfc, 0xf8, 0xc1, 0x97, 0x1f,
|
||||||
0x41, 0xaa, 0x52, 0xb5, 0xc1, 0x8a, 0xb4, 0x81, 0x8e, 0x5e, 0x00, 0xac, 0x5c, 0x6e, 0x65, 0xa8,
|
0xcc, 0x4e, 0xbc, 0xbd, 0x54, 0xa5, 0x6a, 0xed, 0x17, 0x69, 0x6d, 0x1f, 0x3e, 0x03, 0x58, 0xb9,
|
||||||
0x09, 0x6d, 0xc1, 0x30, 0xa0, 0xc0, 0x77, 0xda, 0x6e, 0xfe, 0x71, 0x68, 0xf7, 0xbb, 0x91, 0x2d,
|
0xd8, 0x58, 0xa8, 0x01, 0x6d, 0xc1, 0x30, 0xa0, 0xc0, 0x77, 0x5a, 0x6e, 0xfe, 0x7e, 0x60, 0xf7,
|
||||||
0x18, 0x6a, 0x42, 0x77, 0x16, 0x6b, 0x2e, 0x0d, 0xb6, 0x29, 0xf0, 0xab, 0xd1, 0xb6, 0xa1, 0x63,
|
0x3a, 0x91, 0x2d, 0x18, 0x6a, 0x40, 0x77, 0x1a, 0x6b, 0x2e, 0x0d, 0xb6, 0x29, 0xf0, 0xab, 0xd1,
|
||||||
0xe8, 0x5c, 0x0b, 0xc9, 0xb0, 0x43, 0x81, 0x5f, 0x3f, 0xdb, 0x0f, 0xfe, 0x7c, 0x18, 0x0c, 0x84,
|
0x86, 0xd0, 0x31, 0x74, 0xae, 0x84, 0x64, 0xd8, 0xa1, 0xc0, 0xaf, 0x9d, 0xee, 0x07, 0x7f, 0x2f,
|
||||||
0x64, 0x51, 0x09, 0x20, 0x0f, 0x56, 0x34, 0x8f, 0x99, 0x92, 0x37, 0x77, 0xf8, 0x3f, 0x05, 0x7e,
|
0x07, 0x7d, 0x21, 0x59, 0x54, 0x9a, 0xc8, 0x83, 0x15, 0xcd, 0x63, 0xa6, 0xe4, 0xf5, 0x2d, 0xfe,
|
||||||
0x25, 0xfa, 0xee, 0x85, 0x5c, 0x48, 0xc5, 0x78, 0x86, 0x5d, 0x0a, 0xfc, 0x7f, 0xd1, 0xb6, 0x21,
|
0x4f, 0x81, 0x5f, 0x89, 0xbe, 0xb9, 0x58, 0x11, 0x52, 0x31, 0x9e, 0x61, 0x97, 0x02, 0xff, 0x5f,
|
||||||
0x04, 0x9d, 0x4c, 0xdc, 0x73, 0xbc, 0x53, 0xae, 0x65, 0x3e, 0x89, 0xa0, 0x33, 0xd8, 0xf8, 0xdc,
|
0xb4, 0x21, 0x84, 0xa0, 0x93, 0x89, 0x3b, 0x8e, 0xb7, 0xca, 0x6b, 0x99, 0x8f, 0x22, 0xe8, 0xf4,
|
||||||
0x56, 0x67, 0xd4, 0xbf, 0xea, 0x35, 0x2c, 0xaf, 0xbe, 0x58, 0x52, 0x58, 0xac, 0xad, 0xc4, 0x88,
|
0xd7, 0x7d, 0x6e, 0xb3, 0x3d, 0xec, 0x5d, 0x76, 0xeb, 0x96, 0x57, 0x9b, 0x2f, 0x28, 0x2c, 0xae,
|
||||||
0x39, 0x47, 0x14, 0x56, 0x3b, 0x17, 0xc3, 0x61, 0x7f, 0x34, 0xea, 0x75, 0x1b, 0xc0, 0xdb, 0x5b,
|
0xcd, 0xc4, 0x88, 0x19, 0x47, 0x14, 0x56, 0xdb, 0xe7, 0x83, 0x41, 0x6f, 0x38, 0xec, 0x76, 0xea,
|
||||||
0x2c, 0x69, 0xad, 0x78, 0xee, 0xa8, 0xe9, 0x54, 0x18, 0xc3, 0x99, 0xb7, 0xfb, 0xf8, 0x44, 0xac,
|
0xc0, 0xdb, 0x9d, 0x2f, 0xe8, 0x4e, 0xf1, 0xdc, 0x56, 0x93, 0x89, 0x30, 0x86, 0x33, 0x6f, 0xfb,
|
||||||
0xd7, 0x67, 0x52, 0xba, 0xda, 0x78, 0xb5, 0x26, 0xd6, 0xfb, 0x9a, 0x58, 0x0f, 0x39, 0x01, 0xab,
|
0xe1, 0x91, 0x58, 0x2f, 0x4f, 0xa4, 0xec, 0x6a, 0xe1, 0xe5, 0x8a, 0x58, 0x6f, 0x2b, 0x62, 0xdd,
|
||||||
0x9c, 0x80, 0xb7, 0x9c, 0x80, 0xcf, 0x9c, 0x80, 0xb1, 0x5b, 0x9e, 0xe8, 0xfc, 0x2b, 0x00, 0x00,
|
0xe7, 0x04, 0x2c, 0x73, 0x02, 0x5e, 0x73, 0x02, 0x3e, 0x72, 0x02, 0x46, 0x6e, 0xf9, 0x57, 0x67,
|
||||||
0xff, 0xff, 0xce, 0x7e, 0x6f, 0x85, 0x8f, 0x01, 0x00, 0x00,
|
0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xae, 0x6e, 0x6e, 0xcd, 0xa1, 0x01, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package containerd.v1;
|
package containerd.snapshot.v1;
|
||||||
|
|
||||||
import "gogoproto/gogo.proto";
|
import "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user