Skip to content

Commit a5a9f91

Browse files
committed
Implement Windows snapshotter and differ
This implements the Windows snapshotter and diff Apply function. This allows for Windows layers to be created, and layers to be pulled from the hub. Signed-off-by: Darren Stahl <[email protected]>
1 parent 12eaf13 commit a5a9f91

8 files changed

Lines changed: 577 additions & 33 deletions

File tree

cmd/containerd/builtins_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
_ "github.com/containerd/containerd/diff/windows"
45
_ "github.com/containerd/containerd/snapshots/windows"
56
_ "github.com/containerd/containerd/windows"
67
)

diff/windows/windows.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// +build windows
2+
3+
package windows
4+
5+
import (
6+
"io"
7+
"io/ioutil"
8+
"strings"
9+
"time"
10+
11+
winio "github.com/Microsoft/go-winio"
12+
"github.com/containerd/containerd/archive"
13+
"github.com/containerd/containerd/archive/compression"
14+
"github.com/containerd/containerd/content"
15+
"github.com/containerd/containerd/diff"
16+
"github.com/containerd/containerd/errdefs"
17+
"github.com/containerd/containerd/images"
18+
"github.com/containerd/containerd/log"
19+
"github.com/containerd/containerd/metadata"
20+
"github.com/containerd/containerd/mount"
21+
"github.com/containerd/containerd/platforms"
22+
"github.com/containerd/containerd/plugin"
23+
digest "github.com/opencontainers/go-digest"
24+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
25+
"github.com/pkg/errors"
26+
"github.com/sirupsen/logrus"
27+
"golang.org/x/net/context"
28+
)
29+
30+
func init() {
31+
plugin.Register(&plugin.Registration{
32+
Type: plugin.DiffPlugin,
33+
ID: "windows",
34+
Requires: []plugin.Type{
35+
plugin.MetadataPlugin,
36+
},
37+
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
38+
md, err := ic.Get(plugin.MetadataPlugin)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
44+
return NewWindowsDiff(md.(*metadata.DB).ContentStore())
45+
},
46+
})
47+
}
48+
49+
type windowsDiff struct {
50+
store content.Store
51+
}
52+
53+
var emptyDesc = ocispec.Descriptor{}
54+
55+
// NewWindowsDiff is the Windows container layer implementation of diff.Differ.
56+
func NewWindowsDiff(store content.Store) (diff.Differ, error) {
57+
return &windowsDiff{
58+
store: store,
59+
}, nil
60+
}
61+
62+
// Apply applies the content associated with the provided digests onto the
63+
// provided mounts. Archive content will be extracted and decompressed if
64+
// necessary.
65+
func (s *windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (d ocispec.Descriptor, err error) {
66+
t1 := time.Now()
67+
defer func() {
68+
if err == nil {
69+
log.G(ctx).WithFields(logrus.Fields{
70+
"d": time.Now().Sub(t1),
71+
"dgst": desc.Digest,
72+
"size": desc.Size,
73+
"media": desc.MediaType,
74+
}).Debugf("diff applied")
75+
}
76+
}()
77+
var isCompressed bool
78+
switch desc.MediaType {
79+
case ocispec.MediaTypeImageLayer, images.MediaTypeDockerSchema2Layer:
80+
case ocispec.MediaTypeImageLayerGzip, images.MediaTypeDockerSchema2LayerGzip:
81+
isCompressed = true
82+
default:
83+
// Still apply all generic media types *.tar[.+]gzip and *.tar
84+
if strings.HasSuffix(desc.MediaType, ".tar.gzip") || strings.HasSuffix(desc.MediaType, ".tar+gzip") {
85+
isCompressed = true
86+
} else if !strings.HasSuffix(desc.MediaType, ".tar") {
87+
return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", desc.MediaType)
88+
}
89+
}
90+
91+
ra, err := s.store.ReaderAt(ctx, desc.Digest)
92+
if err != nil {
93+
return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
94+
}
95+
defer ra.Close()
96+
97+
r := content.NewReader(ra)
98+
if isCompressed {
99+
ds, err := compression.DecompressStream(r)
100+
if err != nil {
101+
return emptyDesc, err
102+
}
103+
defer ds.Close()
104+
r = ds
105+
}
106+
107+
digester := digest.Canonical.Digester()
108+
rc := &readCounter{
109+
r: io.TeeReader(r, digester.Hash()),
110+
}
111+
112+
layer, parentLayerPaths, err := mountsToLayerAndParents(mounts)
113+
if err != nil {
114+
return emptyDesc, err
115+
}
116+
117+
// TODO darrenstahlmsft: When this is done isolated, we should disable these.
118+
// it currently cannot be disabled, unless we add ref counting. Since this is
119+
// temporary, leaving it enabled is OK for now.
120+
if err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil {
121+
return emptyDesc, err
122+
}
123+
124+
if _, err := archive.Apply(ctx, layer, rc, archive.WithParentLayers(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil {
125+
return emptyDesc, err
126+
}
127+
128+
// Read any trailing data
129+
if _, err := io.Copy(ioutil.Discard, rc); err != nil {
130+
return emptyDesc, err
131+
}
132+
133+
return ocispec.Descriptor{
134+
MediaType: ocispec.MediaTypeImageLayer,
135+
Size: rc.c,
136+
Digest: digester.Digest(),
137+
}, nil
138+
}
139+
140+
// DiffMounts creates a diff between the given mounts and uploads the result
141+
// to the content store.
142+
func (s *windowsDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) {
143+
panic("not implemented on Windows")
144+
}
145+
146+
type readCounter struct {
147+
r io.Reader
148+
c int64
149+
}
150+
151+
func (rc *readCounter) Read(p []byte) (n int, err error) {
152+
n, err = rc.r.Read(p)
153+
rc.c += int64(n)
154+
return
155+
}
156+
157+
func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
158+
if len(mounts) != 1 {
159+
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows layers")
160+
}
161+
layer := mounts[0].Source
162+
163+
parentLayerPaths, err := mounts[0].GetParentPaths()
164+
if err != nil {
165+
return "", nil, err
166+
}
167+
168+
return layer, parentLayerPaths, nil
169+
}

mount/mount_windows.go

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package mount
22

3-
import "github.com/pkg/errors"
3+
import (
4+
"encoding/json"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/Microsoft/hcsshim"
9+
"github.com/pkg/errors"
10+
)
411

512
var (
613
// ErrNotImplementOnWindows is returned when an action is not implemented for windows
@@ -9,15 +16,73 @@ var (
916

1017
// Mount to the provided target
1118
func (m *Mount) Mount(target string) error {
12-
return ErrNotImplementOnWindows
19+
home, layerID := filepath.Split(m.Source)
20+
21+
parentLayerPaths, err := m.GetParentPaths()
22+
if err != nil {
23+
return err
24+
}
25+
26+
var di = hcsshim.DriverInfo{
27+
HomeDir: home,
28+
}
29+
30+
if err = hcsshim.ActivateLayer(di, layerID); err != nil {
31+
return errors.Wrapf(err, "failed to activate layer %s", m.Source)
32+
}
33+
defer func() {
34+
if err != nil {
35+
hcsshim.DeactivateLayer(di, layerID)
36+
}
37+
}()
38+
39+
if err = hcsshim.PrepareLayer(di, layerID, parentLayerPaths); err != nil {
40+
return errors.Wrapf(err, "failed to prepare layer %s", m.Source)
41+
}
42+
defer func() {
43+
if err != nil {
44+
hcsshim.UnprepareLayer(di, layerID)
45+
}
46+
}()
47+
return nil
48+
}
49+
50+
// ParentLayerPathsFlag is the options flag used to represent the JSON encoded
51+
// list of parent layers required to use the layer
52+
const ParentLayerPathsFlag = "parentLayerPaths="
53+
54+
// GetParentPaths of the mount
55+
func (m *Mount) GetParentPaths() ([]string, error) {
56+
var parentLayerPaths []string
57+
for _, option := range m.Options {
58+
if strings.HasPrefix(option, ParentLayerPathsFlag) {
59+
err := json.Unmarshal([]byte(option[len(ParentLayerPathsFlag):]), &parentLayerPaths)
60+
if err != nil {
61+
return nil, errors.Wrap(err, "failed to unmarshal parent layer paths from mount")
62+
}
63+
}
64+
}
65+
return parentLayerPaths, nil
1366
}
1467

1568
// Unmount the mount at the provided path
1669
func Unmount(mount string, flags int) error {
17-
return ErrNotImplementOnWindows
70+
home, layerID := filepath.Split(mount)
71+
var di = hcsshim.DriverInfo{
72+
HomeDir: home,
73+
}
74+
75+
if err := hcsshim.UnprepareLayer(di, layerID); err != nil {
76+
return errors.Wrapf(err, "failed to unprepare layer %s", mount)
77+
}
78+
if err := hcsshim.DeactivateLayer(di, layerID); err != nil {
79+
return errors.Wrapf(err, "failed to deactivate layer %s", mount)
80+
}
81+
82+
return nil
1883
}
1984

20-
// UnmountAll mounts at the provided path
85+
// UnmountAll unmounts from the provided path
2186
func UnmountAll(mount string, flags int) error {
22-
return ErrNotImplementOnWindows
87+
return Unmount(mount, flags)
2388
}

services/diff/service.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ func init() {
3030
Requires: []plugin.Type{
3131
plugin.DiffPlugin,
3232
},
33-
Config: &config{
34-
Order: []string{"walking"},
35-
},
33+
Config: defaultDifferConfig,
3634
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
3735
differs, err := ic.GetByType(plugin.DiffPlugin)
3836
if err != nil {

services/diff/service_unix.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build !windows
2+
3+
package diff
4+
5+
var defaultDifferConfig = &config{
6+
Order: []string{"walking"},
7+
}

services/diff/service_windows.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build windows
2+
3+
package diff
4+
5+
var defaultDifferConfig = &config{
6+
Order: []string{"windows"},
7+
}

snapshots/windows/utilities.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// +build windows
2+
3+
package windows
4+
5+
import (
6+
"context"
7+
8+
"github.com/containerd/containerd/log"
9+
"github.com/containerd/containerd/snapshots/storage"
10+
)
11+
12+
func rollbackWithLogging(ctx context.Context, t storage.Transactor) {
13+
if err := t.Rollback(); err != nil {
14+
log.G(ctx).WithError(err).Warn("failed to rollback transaction")
15+
}
16+
}

0 commit comments

Comments
 (0)