X Tutup
Skip to content

Commit 2fa366d

Browse files
committed
Add support for multiple differs
Updates the differ service to support calling and configuring multiple differs. The differs are configured as an ordered list of differs which will each be attempting until a supported differ is called. Additionally a not supported error type was added to allow differs to be selective of whether the differ arguments are supported by the differ. This error type corresponds to the GRPC unimplemented error. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
1 parent b2ee0ab commit 2fa366d

File tree

4 files changed

+98
-29
lines changed

4 files changed

+98
-29
lines changed

differ/differ.go

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"io"
55
"io/ioutil"
66
"os"
7+
"strings"
78

89
"github.com/boltdb/bolt"
910
"github.com/containerd/containerd/archive"
1011
"github.com/containerd/containerd/archive/compression"
1112
"github.com/containerd/containerd/content"
13+
"github.com/containerd/containerd/errdefs"
14+
"github.com/containerd/containerd/images"
1215
"github.com/containerd/containerd/metadata"
1316
"github.com/containerd/containerd/mount"
1417
"github.com/containerd/containerd/plugin"
@@ -21,7 +24,7 @@ import (
2124
func init() {
2225
plugin.Register(&plugin.Registration{
2326
Type: plugin.DiffPlugin,
24-
ID: "base-diff",
27+
ID: "walking",
2528
Requires: []plugin.PluginType{
2629
plugin.ContentPlugin,
2730
plugin.MetadataPlugin,
@@ -35,27 +38,38 @@ func init() {
3538
if err != nil {
3639
return nil, err
3740
}
38-
return NewBaseDiff(metadata.NewContentStore(md.(*bolt.DB), c.(content.Store)))
41+
return newWalkingDiff(metadata.NewContentStore(md.(*bolt.DB), c.(content.Store)))
3942
},
4043
})
4144
}
4245

43-
type BaseDiff struct {
46+
type walkingDiff struct {
4447
store content.Store
4548
}
4649

47-
var _ plugin.Differ = &BaseDiff{}
48-
4950
var emptyDesc = ocispec.Descriptor{}
5051

51-
func NewBaseDiff(store content.Store) (*BaseDiff, error) {
52-
return &BaseDiff{
52+
func newWalkingDiff(store content.Store) (plugin.Differ, error) {
53+
return &walkingDiff{
5354
store: store,
5455
}, nil
5556
}
5657

57-
func (s *BaseDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (ocispec.Descriptor, error) {
58-
// TODO: Check for supported media types
58+
func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (ocispec.Descriptor, error) {
59+
var isCompressed bool
60+
switch desc.MediaType {
61+
case ocispec.MediaTypeImageLayer, images.MediaTypeDockerSchema2Layer:
62+
case ocispec.MediaTypeImageLayerGzip, images.MediaTypeDockerSchema2LayerGzip:
63+
isCompressed = true
64+
default:
65+
// Still apply all generic media types *.tar[.+]gzip and *.tar
66+
if strings.HasSuffix(desc.MediaType, ".tar.gzip") || strings.HasSuffix(desc.MediaType, ".tar+gzip") {
67+
isCompressed = true
68+
} else if !strings.HasSuffix(desc.MediaType, ".tar") {
69+
return emptyDesc, errors.Wrapf(errdefs.ErrNotSupported, "unsupported diff media type: %v", desc.MediaType)
70+
}
71+
}
72+
5973
dir, err := ioutil.TempDir("", "extract-")
6074
if err != nil {
6175
return emptyDesc, errors.Wrap(err, "failed to create temporary directory")
@@ -67,22 +81,25 @@ func (s *BaseDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []
6781
}
6882
defer mount.Unmount(dir, 0)
6983

70-
r, err := s.store.ReaderAt(ctx, desc.Digest)
84+
ra, err := s.store.ReaderAt(ctx, desc.Digest)
7185
if err != nil {
7286
return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
7387
}
74-
defer r.Close()
88+
defer ra.Close()
7589

76-
// TODO: only decompress stream if media type is compressed
77-
ds, err := compression.DecompressStream(content.NewReader(r))
78-
if err != nil {
79-
return emptyDesc, err
90+
r := content.NewReader(ra)
91+
if isCompressed {
92+
ds, err := compression.DecompressStream(r)
93+
if err != nil {
94+
return emptyDesc, err
95+
}
96+
defer ds.Close()
97+
r = ds
8098
}
81-
defer ds.Close()
8299

83100
digester := digest.Canonical.Digester()
84101
rc := &readCounter{
85-
r: io.TeeReader(ds, digester.Hash()),
102+
r: io.TeeReader(r, digester.Hash()),
86103
}
87104

88105
if _, err := archive.Apply(ctx, dir, rc); err != nil {
@@ -101,7 +118,7 @@ func (s *BaseDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []
101118
}, nil
102119
}
103120

104-
func (s *BaseDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, media, ref string) (ocispec.Descriptor, error) {
121+
func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, media, ref string) (ocispec.Descriptor, error) {
105122
var isCompressed bool
106123
switch media {
107124
case ocispec.MediaTypeImageLayer:
@@ -111,7 +128,7 @@ func (s *BaseDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, m
111128
media = ocispec.MediaTypeImageLayerGzip
112129
isCompressed = true
113130
default:
114-
return emptyDesc, errors.Errorf("unsupported diff media type: %v", media)
131+
return emptyDesc, errors.Wrapf(errdefs.ErrNotSupported, "unsupported diff media type: %v", media)
115132
}
116133
aDir, err := ioutil.TempDir("", "left-")
117134
if err != nil {

errdefs/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var (
2626
ErrAlreadyExists = errors.New("already exists")
2727
ErrFailedPrecondition = errors.New("failed precondition")
2828
ErrUnavailable = errors.New("unavailable")
29+
ErrNotSupported = errors.New("not supported") // represents not supported and unimplemented
2930
)
3031

3132
func IsInvalidArgument(err error) bool {
@@ -52,3 +53,7 @@ func IsFailedPrecondition(err error) bool {
5253
func IsUnavailable(err error) bool {
5354
return errors.Cause(err) == ErrUnavailable
5455
}
56+
57+
func IsNotSupported(err error) bool {
58+
return errors.Cause(err) == ErrNotSupported
59+
}

errdefs/grpc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ func ToGRPC(err error) error {
3838
return grpc.Errorf(codes.FailedPrecondition, err.Error())
3939
case IsUnavailable(err):
4040
return grpc.Errorf(codes.Unavailable, err.Error())
41+
case IsNotSupported(err):
42+
return grpc.Errorf(codes.Unimplemented, err.Error())
4143
}
4244

4345
return err
@@ -69,6 +71,8 @@ func FromGRPC(err error) error {
6971
cls = ErrUnavailable
7072
case codes.FailedPrecondition:
7173
cls = ErrFailedPrecondition
74+
case codes.Unimplemented:
75+
cls = ErrNotSupported
7276
default:
7377
cls = ErrUnknown
7478
}

services/diff/service.go

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,57 @@ import (
66
"github.com/containerd/containerd/errdefs"
77
"github.com/containerd/containerd/mount"
88
"github.com/containerd/containerd/plugin"
9+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
10+
"github.com/pkg/errors"
911
"golang.org/x/net/context"
1012
"google.golang.org/grpc"
1113
)
1214

15+
type config struct {
16+
// Order is the order of preference in which to try diff algorithms, the
17+
// first differ which is supported is used.
18+
// Note when multiple differs may be supported, this order will be
19+
// respected for which is choosen. Each differ should return the same
20+
// correct output, allowing any ordering to be used to prefer
21+
// more optimimal implementations.
22+
Order []string `toml:"default,omitempty"`
23+
}
24+
1325
func init() {
1426
plugin.Register(&plugin.Registration{
1527
Type: plugin.GRPCPlugin,
1628
ID: "diff",
1729
Requires: []plugin.PluginType{
1830
plugin.DiffPlugin,
1931
},
32+
Config: &config{
33+
Order: []string{"walking"},
34+
},
2035
Init: func(ic *plugin.InitContext) (interface{}, error) {
21-
d, err := ic.Get(plugin.DiffPlugin)
36+
differs, err := ic.GetAll(plugin.DiffPlugin)
2237
if err != nil {
2338
return nil, err
2439
}
40+
41+
orderedNames := ic.Config.(*config).Order
42+
ordered := make([]plugin.Differ, len(orderedNames))
43+
for i, n := range orderedNames {
44+
differ, ok := differs[n]
45+
if !ok {
46+
return nil, errors.Errorf("needed differ not loaded: %s", n)
47+
}
48+
ordered[i] = differ.(plugin.Differ)
49+
}
50+
2551
return &service{
26-
diff: d.(plugin.Differ),
52+
differs: ordered,
2753
}, nil
2854
},
2955
})
3056
}
3157

3258
type service struct {
33-
diff plugin.Differ
59+
differs []plugin.Differ
3460
}
3561

3662
func (s *service) Register(gs *grpc.Server) error {
@@ -39,12 +65,20 @@ func (s *service) Register(gs *grpc.Server) error {
3965
}
4066

4167
func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi.ApplyResponse, error) {
42-
desc := toDescriptor(er.Diff)
43-
// TODO: Check for supported media types
68+
var (
69+
ocidesc ocispec.Descriptor
70+
err error
71+
desc = toDescriptor(er.Diff)
72+
mounts = toMounts(er.Mounts)
73+
)
4474

45-
mounts := toMounts(er.Mounts)
75+
for _, differ := range s.differs {
76+
ocidesc, err = differ.Apply(ctx, desc, mounts)
77+
if !errdefs.IsNotSupported(err) {
78+
break
79+
}
80+
}
4681

47-
ocidesc, err := s.diff.Apply(ctx, desc, mounts)
4882
if err != nil {
4983
return nil, errdefs.ToGRPC(err)
5084
}
@@ -56,10 +90,19 @@ func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi
5690
}
5791

5892
func (s *service) Diff(ctx context.Context, dr *diffapi.DiffRequest) (*diffapi.DiffResponse, error) {
59-
aMounts := toMounts(dr.Left)
60-
bMounts := toMounts(dr.Right)
93+
var (
94+
ocidesc ocispec.Descriptor
95+
err error
96+
aMounts = toMounts(dr.Left)
97+
bMounts = toMounts(dr.Right)
98+
)
6199

62-
ocidesc, err := s.diff.DiffMounts(ctx, aMounts, bMounts, dr.MediaType, dr.Ref)
100+
for _, differ := range s.differs {
101+
ocidesc, err = differ.DiffMounts(ctx, aMounts, bMounts, dr.MediaType, dr.Ref)
102+
if !errdefs.IsNotSupported(err) {
103+
break
104+
}
105+
}
63106
if err != nil {
64107
return nil, errdefs.ToGRPC(err)
65108
}

0 commit comments

Comments
 (0)
X Tutup