Merge pull request #614 from dmcgowan/rootfs-service

Rootfs service
This commit is contained in:
Stephen Day 2017-03-15 16:44:03 -07:00 committed by GitHub
commit 82a2d766ec
14 changed files with 2136 additions and 6 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
syntax = "proto3";
package containerd.v1;
import "gogoproto/gogo.proto";
import "github.com/docker/containerd/api/types/mount/mount.proto";
import "github.com/docker/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;
}

View file

@ -0,0 +1,422 @@
// Code generated by protoc-gen-gogo.
// source: github.com/docker/containerd/api/types/descriptor/descriptor.proto
// DO NOT EDIT!
/*
Package descriptor is a generated protocol buffer package.
It is generated from these files:
github.com/docker/containerd/api/types/descriptor/descriptor.proto
It has these top-level messages:
Descriptor
*/
package descriptor
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import github_com_opencontainers_go_digest "github.com/opencontainers/go-digest"
import strings "strings"
import reflect "reflect"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
// Descriptor describes a blob in a content store.
//
// This descriptor can be used to reference content from an
// oci descriptor found in a manifest.
// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor
type Descriptor struct {
MediaType string `protobuf:"bytes,1,opt,name=mediaType,proto3" json:"mediaType,omitempty"`
Digest github_com_opencontainers_go_digest.Digest `protobuf:"bytes,2,opt,name=digest,proto3,customtype=github.com/opencontainers/go-digest.Digest" json:"digest"`
Size_ int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
}
func (m *Descriptor) Reset() { *m = Descriptor{} }
func (*Descriptor) ProtoMessage() {}
func (*Descriptor) Descriptor() ([]byte, []int) { return fileDescriptorDescriptor, []int{0} }
func init() {
proto.RegisterType((*Descriptor)(nil), "containerd.v1.types.Descriptor")
}
func (m *Descriptor) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Descriptor) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.MediaType) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintDescriptor(dAtA, i, uint64(len(m.MediaType)))
i += copy(dAtA[i:], m.MediaType)
}
if len(m.Digest) > 0 {
dAtA[i] = 0x12
i++
i = encodeVarintDescriptor(dAtA, i, uint64(len(m.Digest)))
i += copy(dAtA[i:], m.Digest)
}
if m.Size_ != 0 {
dAtA[i] = 0x18
i++
i = encodeVarintDescriptor(dAtA, i, uint64(m.Size_))
}
return i, nil
}
func encodeFixed64Descriptor(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Descriptor(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintDescriptor(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *Descriptor) Size() (n int) {
var l int
_ = l
l = len(m.MediaType)
if l > 0 {
n += 1 + l + sovDescriptor(uint64(l))
}
l = len(m.Digest)
if l > 0 {
n += 1 + l + sovDescriptor(uint64(l))
}
if m.Size_ != 0 {
n += 1 + sovDescriptor(uint64(m.Size_))
}
return n
}
func sovDescriptor(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozDescriptor(x uint64) (n int) {
return sovDescriptor(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (this *Descriptor) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&Descriptor{`,
`MediaType:` + fmt.Sprintf("%v", this.MediaType) + `,`,
`Digest:` + fmt.Sprintf("%v", this.Digest) + `,`,
`Size_:` + fmt.Sprintf("%v", this.Size_) + `,`,
`}`,
}, "")
return s
}
func valueToStringDescriptor(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
func (m *Descriptor) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDescriptor
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Descriptor: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Descriptor: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field MediaType", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDescriptor
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthDescriptor
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.MediaType = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Digest", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDescriptor
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthDescriptor
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Digest = github_com_opencontainers_go_digest.Digest(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Size_", wireType)
}
m.Size_ = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDescriptor
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Size_ |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipDescriptor(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthDescriptor
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipDescriptor(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDescriptor
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDescriptor
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDescriptor
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthDescriptor
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDescriptor
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipDescriptor(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthDescriptor = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDescriptor = fmt.Errorf("proto: integer overflow")
)
func init() {
proto.RegisterFile("github.com/docker/containerd/api/types/descriptor/descriptor.proto", fileDescriptorDescriptor)
}
var fileDescriptorDescriptor = []byte{
// 231 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x72, 0x4a, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xc9, 0x4f, 0xce, 0x4e, 0x2d, 0xd2, 0x4f, 0xce,
0xcf, 0x2b, 0x49, 0xcc, 0xcc, 0x4b, 0x2d, 0x4a, 0xd1, 0x4f, 0x2c, 0xc8, 0xd4, 0x2f, 0xa9, 0x2c,
0x48, 0x2d, 0xd6, 0x4f, 0x49, 0x2d, 0x4e, 0x2e, 0xca, 0x2c, 0x28, 0xc9, 0x2f, 0x42, 0x62, 0xea,
0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x09, 0x23, 0x74, 0xe8, 0x95, 0x19, 0xea, 0x81, 0x35, 0x48,
0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xe5, 0xf5, 0x41, 0x2c, 0x88, 0x52, 0xa5, 0x2e, 0x46, 0x2e,
0x2e, 0x17, 0xb8, 0x7e, 0x21, 0x19, 0x2e, 0xce, 0xdc, 0xd4, 0x94, 0xcc, 0xc4, 0x90, 0xca, 0x82,
0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x84, 0x80, 0x90, 0x17, 0x17, 0x5b, 0x4a, 0x66,
0x7a, 0x6a, 0x71, 0x89, 0x04, 0x13, 0x48, 0xca, 0xc9, 0xe8, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7,
0xe4, 0xb5, 0x90, 0xdc, 0x9c, 0x5f, 0x90, 0x9a, 0x07, 0xb7, 0xbe, 0x58, 0x3f, 0x3d, 0x5f, 0x17,
0xa2, 0x45, 0xcf, 0x05, 0x4c, 0x05, 0x41, 0x4d, 0x10, 0x12, 0xe2, 0x62, 0x29, 0xce, 0xac, 0x4a,
0x95, 0x60, 0x56, 0x60, 0xd4, 0x60, 0x0e, 0x02, 0xb3, 0x9d, 0x24, 0x4e, 0x3c, 0x94, 0x63, 0xb8,
0xf1, 0x50, 0x8e, 0xa1, 0xe1, 0x91, 0x1c, 0xe3, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31,
0x3e, 0x78, 0x24, 0xc7, 0x98, 0xc4, 0x06, 0x76, 0xad, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x37,
0xe8, 0x43, 0x79, 0x1e, 0x01, 0x00, 0x00,
}

View file

@ -0,0 +1,16 @@
syntax = "proto3";
package containerd.v1.types;
import "gogoproto/gogo.proto";
// Descriptor describes a blob in a content store.
//
// This descriptor can be used to reference content from an
// oci descriptor found in a manifest.
// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor
message Descriptor {
string mediaType = 1;
string digest = 2 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
int64 size = 3;
}

View file

@ -7,6 +7,7 @@ import (
_ "github.com/docker/containerd/services/execution" _ "github.com/docker/containerd/services/execution"
_ "github.com/docker/containerd/services/healthcheck" _ "github.com/docker/containerd/services/healthcheck"
_ "github.com/docker/containerd/services/metrics" _ "github.com/docker/containerd/services/metrics"
_ "github.com/docker/containerd/services/rootfs"
_ "github.com/docker/containerd/snapshot/btrfs" _ "github.com/docker/containerd/snapshot/btrfs"
_ "github.com/docker/containerd/snapshot/overlay" _ "github.com/docker/containerd/snapshot/overlay"
) )

View file

@ -19,6 +19,7 @@ import (
"github.com/docker/containerd" "github.com/docker/containerd"
contentapi "github.com/docker/containerd/api/services/content" contentapi "github.com/docker/containerd/api/services/content"
api "github.com/docker/containerd/api/services/execution" api "github.com/docker/containerd/api/services/execution"
rootfsapi "github.com/docker/containerd/api/services/rootfs"
"github.com/docker/containerd/content" "github.com/docker/containerd/content"
"github.com/docker/containerd/log" "github.com/docker/containerd/log"
"github.com/docker/containerd/plugin" "github.com/docker/containerd/plugin"
@ -366,6 +367,8 @@ 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")
default: default:
fmt.Printf("unknown GRPC server type: %#v\n", info.Server) fmt.Printf("unknown GRPC server type: %#v\n", info.Server)
} }

1
cmd/dist/main.go vendored
View file

@ -69,6 +69,7 @@ distribution tool
deleteCommand, deleteCommand,
listCommand, listCommand,
applyCommand, applyCommand,
rootfsCommand,
} }
app.Before = func(context *cli.Context) error { app.Before = func(context *cli.Context) error {
var ( var (

142
cmd/dist/rootfs.go vendored Normal file
View file

@ -0,0 +1,142 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
contentapi "github.com/docker/containerd/api/services/content"
rootfsapi "github.com/docker/containerd/api/services/rootfs"
"github.com/docker/containerd/content"
"github.com/docker/containerd/log"
contentservice "github.com/docker/containerd/services/content"
rootfsservice "github.com/docker/containerd/services/rootfs"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli"
)
var rootfsCommand = cli.Command{
Name: "rootfs",
Usage: "rootfs setups a rootfs",
Subcommands: []cli.Command{
rootfsPrepareCommand,
rootfsInitCommand,
},
}
var rootfsPrepareCommand = cli.Command{
Name: "unpack",
Usage: "unpack applies layers from a manifest to a snapshot",
ArgsUsage: "[flags] <digest>",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
var (
ctx = background
)
dgst, err := digest.Parse(clicontext.Args().First())
if err != nil {
return err
}
log.G(ctx).Infof("unpacking layers from manifest %s", dgst.String())
conn, err := connectGRPC(clicontext)
if err != nil {
return err
}
provider := contentservice.NewProviderFromClient(contentapi.NewContentClient(conn))
m, err := resolveManifest(ctx, provider, dgst)
if err != nil {
return err
}
unpacker := rootfsservice.NewUnpackerFromClient(rootfsapi.NewRootFSClient(conn))
chainID, err := unpacker.Unpack(ctx, m.Layers)
if err != nil {
return err
}
log.G(ctx).Infof("chain ID: %s", chainID.String())
return nil
},
}
var rootfsInitCommand = cli.Command{
Name: "prepare",
Usage: "prepare gets mount commands for digest",
ArgsUsage: "[flags] <digest> <target>",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
var (
ctx = background
)
if clicontext.NArg() != 2 {
return cli.ShowSubcommandHelp(clicontext)
}
dgst, err := digest.Parse(clicontext.Args().Get(0))
if err != nil {
return err
}
target := clicontext.Args().Get(1)
log.G(ctx).Infof("preparing mounts %s", dgst.String())
conn, err := connectGRPC(clicontext)
if err != nil {
return err
}
rclient := rootfsapi.NewRootFSClient(conn)
ir := &rootfsapi.PrepareRequest{
Name: target,
ChainID: dgst,
}
resp, err := rclient.Prepare(ctx, ir)
if err != nil {
return err
}
for _, m := range resp.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)
}

View file

@ -16,9 +16,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type Unpacker interface {
Unpack(ctx context.Context, layers []ocispec.Descriptor) (digest.Digest, error)
}
type Mounter interface { type Mounter interface {
Mount(mounts ...containerd.Mount) error Mount(target string, mounts ...containerd.Mount) error
Unmount(mounts ...containerd.Mount) error Unmount(target string) error
} }
// ApplyLayer applies the layer to the provided parent. The resulting snapshot // ApplyLayer applies the layer to the provided parent. The resulting snapshot
@ -51,13 +55,13 @@ func ApplyLayer(snapshots snapshot.Snapshotter, mounter Mounter, rd io.Reader, p
return "", err return "", err
} }
if err := mounter.Mount(mounts...); err != nil { if err := mounter.Mount(dir, mounts...); err != nil {
if err := snapshots.Remove(ctx, key); err != nil { if err := snapshots.Remove(ctx, key); err != nil {
log.L.WithError(err).Error("snapshot rollback failed") log.L.WithError(err).Error("snapshot rollback failed")
} }
return "", err return "", err
} }
defer mounter.Unmount(mounts...) defer mounter.Unmount(dir)
rd, err = dockerarchive.DecompressStream(rd) rd, err = dockerarchive.DecompressStream(rd)
if err != nil { if err != nil {
@ -74,6 +78,9 @@ func ApplyLayer(snapshots snapshot.Snapshotter, mounter Mounter, rd io.Reader, p
if parent != "" { if parent != "" {
chainID = identity.ChainID([]digest.Digest{parent, chainID}) chainID = identity.ChainID([]digest.Digest{parent, chainID})
} }
if _, err := snapshots.Stat(ctx, chainID.String()); err == nil {
return diffID, nil //TODO: call snapshots.Remove(ctx, key) once implemented
}
return diffID, snapshots.Commit(ctx, chainID.String(), key) return diffID, snapshots.Commit(ctx, chainID.String(), key)
} }
@ -90,7 +97,7 @@ func Prepare(ctx context.Context, snapshots snapshot.Snapshotter, mounter Mounte
// rootfs Controller. // rootfs Controller.
// //
// Just pass them in for now. // Just pass them in for now.
openBlob func(digest.Digest) (io.ReadCloser, error), openBlob func(context.Context, digest.Digest) (io.ReadCloser, error),
resolveDiffID func(digest.Digest) digest.Digest, resolveDiffID func(digest.Digest) digest.Digest,
registerDiffID func(diffID, dgst digest.Digest) error) (digest.Digest, error) { registerDiffID func(diffID, dgst digest.Digest) error) (digest.Digest, error) {
var ( var (
@ -116,7 +123,7 @@ func Prepare(ctx context.Context, snapshots snapshot.Snapshotter, mounter Mounte
} }
} }
rc, err := openBlob(layerDigest) rc, err := openBlob(ctx, layerDigest)
if err != nil { if err != nil {
return "", err return "", err
} }

94
rootfs/init.go Normal file
View file

@ -0,0 +1,94 @@
package rootfs
import (
"context"
"fmt"
"io/ioutil"
"os"
"github.com/docker/containerd"
"github.com/docker/containerd/log"
"github.com/docker/containerd/snapshot"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
var (
initializers = map[string]initializerFunc{}
)
type initializerFunc func(string) 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)
if err == nil {
return nil, errors.Errorf("rootfs already exists")
}
// TODO: ensure not exist error once added to snapshot package
parentS := parent.String()
initName := defaultInitializer
initFn := initializers[initName]
if initFn != nil {
parentS, err = createInitLayer(ctx, parentS, initName, initFn, snapshotter, mounter)
if err != nil {
return nil, err
}
}
if readonly {
return snapshotter.View(ctx, name, parentS)
}
return snapshotter.Prepare(ctx, name, parentS)
}
func createInitLayer(ctx context.Context, parent, initName string, initFn func(string) error, snapshotter snapshot.Snapshotter, mounter Mounter) (string, error) {
initS := fmt.Sprintf("%s %s", parent, initName)
if _, err := snapshotter.Stat(ctx, initS); err == nil {
return initS, nil
}
// TODO: ensure not exist error once added to snapshot package
// Create tempdir
td, err := ioutil.TempDir("", "create-init-")
if err != nil {
return "", err
}
defer os.RemoveAll(td)
mounts, err := snapshotter.Prepare(ctx, td, parent)
if err != nil {
return "", err
}
defer func() {
if err != nil {
// TODO: once implemented uncomment
//if rerr := snapshotter.Remove(ctx, td); rerr != nil {
// log.G(ctx).Errorf("Failed to remove snapshot %s: %v", td, merr)
//}
}
}()
if err = mounter.Mount(td, mounts...); err != nil {
return "", err
}
if err = initFn(td); err != nil {
if merr := mounter.Unmount(td); merr != nil {
log.G(ctx).Errorf("Failed to unmount %s: %v", td, merr)
}
return "", err
}
if err = mounter.Unmount(td); err != nil {
return "", err
}
if err := snapshotter.Commit(ctx, initS, td); err != nil {
return "", err
}
return initS, nil
}

114
rootfs/init_linux.go Normal file
View file

@ -0,0 +1,114 @@
package rootfs
import (
"os"
"path/filepath"
"syscall"
)
const (
defaultInitializer = "linux-init"
)
func init() {
initializers[defaultInitializer] = initFS
}
func createDirectory(name string, uid, gid int) initializerFunc {
return func(root string) error {
dname := filepath.Join(root, name)
st, err := os.Stat(dname)
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
if st.IsDir() {
stat := st.Sys().(*syscall.Stat_t)
if int(stat.Gid) == gid && int(stat.Uid) == uid {
return nil
}
} else {
if err := os.Remove(dname); err != nil {
return err
}
if err := os.Mkdir(dname, 0755); err != nil {
return err
}
}
} else {
if err := os.Mkdir(dname, 0755); err != nil {
return err
}
}
return os.Chown(dname, uid, gid)
}
}
func touchFile(name string, uid, gid int) initializerFunc {
return func(root string) error {
fname := filepath.Join(root, name)
st, err := os.Stat(fname)
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
stat := st.Sys().(*syscall.Stat_t)
if int(stat.Gid) == gid && int(stat.Uid) == uid {
return nil
}
return os.Chown(fname, uid, gid)
}
f, err := os.OpenFile(fname, os.O_CREATE, 0644)
if err != nil {
return err
}
defer f.Close()
return f.Chown(uid, gid)
}
}
func symlink(oldname, newname string) initializerFunc {
return func(root string) error {
linkName := filepath.Join(root, newname)
if _, err := os.Stat(linkName); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
return nil
}
return os.Symlink(oldname, linkName)
}
}
func initFS(root string) error {
st, err := os.Stat(root)
if err != nil {
return err
}
stat := st.Sys().(*syscall.Stat_t)
uid := int(stat.Uid)
gid := int(stat.Gid)
initFuncs := []initializerFunc{
createDirectory("/dev", uid, gid),
createDirectory("/dev/pts", uid, gid),
createDirectory("/dev/shm", uid, gid),
touchFile("/dev/console", uid, gid),
createDirectory("/proc", uid, gid),
createDirectory("/sys", uid, gid),
createDirectory("/etc", uid, gid),
touchFile("/etc/resolv.conf", uid, gid),
touchFile("/etc/hosts", uid, gid),
touchFile("/etc/hostname", uid, gid),
symlink("/proc/mounts", "/etc/mtab"),
}
for _, fn := range initFuncs {
if err := fn(root); err != nil {
return err
}
}
return nil
}

7
rootfs/init_other.go Normal file
View file

@ -0,0 +1,7 @@
// +build !linux
package rootfs
const (
defaultInitializer = ""
)

View file

@ -0,0 +1,39 @@
package rootfs
import (
"context"
rootfsapi "github.com/docker/containerd/api/services/rootfs"
containerd_v1_types "github.com/docker/containerd/api/types/descriptor"
"github.com/docker/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 "", nil
}
return resp.ChainID, nil
}

115
services/rootfs/service.go Normal file
View file

@ -0,0 +1,115 @@
package rootfs
import (
"syscall"
"github.com/docker/containerd"
rootfsapi "github.com/docker/containerd/api/services/rootfs"
containerd_v1_types "github.com/docker/containerd/api/types/mount"
"github.com/docker/containerd/content"
"github.com/docker/containerd/log"
"github.com/docker/containerd/plugin"
"github.com/docker/containerd/rootfs"
"github.com/docker/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"
)
func init() {
plugin.Register("rootfs-grpc", &plugin.Registration{
Type: plugin.GRPCPlugin,
Init: func(ic *plugin.InitContext) (interface{}, error) {
return NewService(ic.Store, 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, 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 syscall.Unmount(dir, 0)
}
func emptyResolver(digest.Digest) digest.Digest {
return digest.Digest("")
}
func noopRegister(digest.Digest, digest.Digest) error {
return nil
}