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:
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
36
cmd/dist/common.go
vendored
@@ -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
38
cmd/dist/pull.go
vendored
@@ -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
78
cmd/dist/rootfs.go
vendored
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user