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:
Derek McGowan
2017-05-08 13:47:58 -07:00
parent a622f5e726
commit 098ff94b24
23 changed files with 4381 additions and 1571 deletions

View File

@@ -3,10 +3,11 @@ package main
// register containerd builtins here
import (
_ "github.com/containerd/containerd/services/content"
_ "github.com/containerd/containerd/services/diff"
_ "github.com/containerd/containerd/services/execution"
_ "github.com/containerd/containerd/services/healthcheck"
_ "github.com/containerd/containerd/services/images"
_ "github.com/containerd/containerd/services/metrics"
_ "github.com/containerd/containerd/services/rootfs"
_ "github.com/containerd/containerd/services/snapshot"
_ "github.com/containerd/containerd/services/version"
)

View File

@@ -18,9 +18,10 @@ import (
"github.com/Sirupsen/logrus"
contentapi "github.com/containerd/containerd/api/services/content"
diffapi "github.com/containerd/containerd/api/services/diff"
api "github.com/containerd/containerd/api/services/execution"
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"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
@@ -417,14 +418,16 @@ func interceptor(ctx gocontext.Context,
ctx = log.WithModule(ctx, "execution")
case contentapi.ContentServer:
ctx = log.WithModule(ctx, "content")
case rootfsapi.RootFSServer:
ctx = log.WithModule(ctx, "rootfs")
case imagesapi.ImagesServer:
ctx = log.WithModule(ctx, "images")
case grpc_health_v1.HealthServer:
// No need to change the context
case versionapi.VersionServer:
ctx = log.WithModule(ctx, "version")
case snapshotapi.SnapshotServer:
ctx = log.WithModule(ctx, "snapshot")
case diffapi.DiffServer:
ctx = log.WithModule(ctx, "diff")
default:
log.G(ctx).Warnf("unknown GRPC server type: %#v\n", info.Server)
}

View File

@@ -7,14 +7,13 @@ import (
"os"
"runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
"github.com/containerd/containerd"
"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/snapshot"
"github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@@ -59,7 +58,7 @@ var runCommand = cli.Command{
Action: func(context *cli.Context) error {
var (
err error
resp *rootfsapi.MountResponse
mounts []containerd.Mount
imageConfig ocispec.Image
ctx = gocontext.Background()
@@ -87,7 +86,7 @@ var runCommand = cli.Command{
return err
}
rootfsClient, err := getRootFSService(context)
snapshotter, err := getSnapshotter(context)
if err != nil {
return err
}
@@ -111,20 +110,15 @@ var runCommand = cli.Command{
return err
}
if _, err := rootfsClient.Prepare(gocontext.TODO(), &rootfsapi.PrepareRequest{
Name: id,
ChainID: identity.ChainID(diffIDs),
}); err != nil {
if grpc.Code(err) != codes.AlreadyExists {
mounts, err = snapshotter.Prepare(ctx, id, identity.ChainID(diffIDs).String())
if err != nil {
if !snapshot.IsExist(err) {
return err
}
mounts, err = snapshotter.Mounts(ctx, id)
if err != nil {
return err
}
}
resp, err = rootfsClient.Mounts(gocontext.TODO(), &rootfsapi.MountsRequest{
Name: id,
})
if err != nil {
return err
}
ic, err := image.Config(ctx, content)
@@ -153,8 +147,13 @@ var runCommand = cli.Command{
if err != nil {
return err
}
if resp != nil {
create.Rootfs = resp.Mounts
for _, m := range mounts {
create.Rootfs = append(create.Rootfs, &mounttypes.Mount{
Type: m.Type,
Source: m.Source,
Options: m.Options,
})
}
var con console.Console
if create.Terminal {

View File

@@ -15,13 +15,15 @@ import (
contentapi "github.com/containerd/containerd/api/services/content"
"github.com/containerd/containerd/api/services/execution"
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"
"github.com/containerd/containerd/api/types/container"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
contentservice "github.com/containerd/containerd/services/content"
imagesservice "github.com/containerd/containerd/services/images"
snapshotservice "github.com/containerd/containerd/services/snapshot"
"github.com/containerd/containerd/snapshot"
"github.com/urfave/cli"
"google.golang.org/grpc"
)
@@ -44,12 +46,12 @@ func getContentStore(context *cli.Context) (content.Store, error) {
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)
if err != nil {
return nil, err
}
return rootfsapi.NewRootFSClient(conn), nil
return snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn)), nil
}
func getImageStore(clicontext *cli.Context) (images.Store, error) {

36
cmd/dist/common.go vendored
View File

@@ -4,6 +4,7 @@ import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
@@ -18,8 +19,10 @@ import (
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/rootfs"
contentservice "github.com/containerd/containerd/services/content"
imagesservice "github.com/containerd/containerd/services/images"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/urfave/cli"
"google.golang.org/grpc"
@@ -151,3 +154,36 @@ func passwordPrompt() (string, error) {
}
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
View File

@@ -2,19 +2,19 @@ package main
import (
"context"
"encoding/json"
"os"
"text/tabwriter"
"time"
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
"github.com/containerd/containerd/content"
diffapi "github.com/containerd/containerd/api/services/diff"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/progress"
"github.com/containerd/containerd/remotes"
rootfsservice "github.com/containerd/containerd/services/rootfs"
"github.com/opencontainers/image-spec/identity"
"github.com/containerd/containerd/rootfs"
diffservice "github.com/containerd/containerd/services/diff"
snapshotservice "github.com/containerd/containerd/services/snapshot"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli"
"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
// root filesystem chainid for the image. For now, we just print
// it, but we should keep track of this in the metadata storage.
image, err := imageStore.Get(ctx, resolvedImageName)
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 {
log.G(ctx).Fatal(err)
}
var manifest ocispec.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
log.G(ctx).Fatal(err)
log.G(ctx).WithError(err).Fatal("Failed to get rootfs layers")
}
conn, err := connectGRPC(clicontext)
if err != nil {
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")
chainID, err := rootfs.Unpack(ctx, manifest.Layers)
chainID, err := rootfs.ApplyLayers(ctx, layers, snapshotter, applier)
if err != nil {
log.G(ctx).Fatal(err)
}
diffIDs, err := image.RootFS(ctx, cs)
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")
}
log.G(ctx).Infof("Unpacked chain id: %s", chainID)
}()
var (

78
cmd/dist/rootfs.go vendored
View File

@@ -1,17 +1,17 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
rootfsapi "github.com/containerd/containerd/api/services/rootfs"
"github.com/containerd/containerd/content"
diffapi "github.com/containerd/containerd/api/services/diff"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
"github.com/containerd/containerd/images"
"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"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli"
@@ -42,23 +42,32 @@ var rootfsUnpackCommand = cli.Command{
log.G(ctx).Infof("unpacking layers from manifest %s", dgst.String())
conn, err := connectGRPC(clicontext)
if err != nil {
return err
}
cs, err := resolveContentStore(clicontext)
if err != nil {
return err
}
m, err := resolveManifest(ctx, cs, dgst)
if err != nil {
return err
image := images.Image{
Target: ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: dgst,
},
}
unpacker := rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn))
chainID, err := unpacker.Unpack(ctx, m.Layers)
layers, err := getImageLayers(ctx, image, cs)
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 {
return err
}
@@ -95,48 +104,17 @@ var rootfsPrepareCommand = cli.Command{
return err
}
rclient := rootfsapi.NewRootFSClient(conn)
snapshotter := snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotClient(conn))
ir := &rootfsapi.PrepareRequest{
Name: target,
ChainID: dgst,
}
resp, err := rclient.Prepare(ctx, ir)
mounts, err := snapshotter.Prepare(ctx, target, dgst.String())
if err != nil {
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, ","))
}
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)
}