@@ -20,12 +20,16 @@ package windows
2020
2121import (
2222 "context"
23+ "crypto/rand"
24+ "encoding/base64"
25+ "fmt"
2326 "io"
2427 "io/ioutil"
2528 "time"
2629
2730 winio "github.com/Microsoft/go-winio"
2831 "github.com/containerd/containerd/archive"
32+ "github.com/containerd/containerd/archive/compression"
2933 "github.com/containerd/containerd/content"
3034 "github.com/containerd/containerd/diff"
3135 "github.com/containerd/containerd/errdefs"
@@ -73,6 +77,7 @@ type windowsDiff struct {
7377}
7478
7579var emptyDesc = ocispec.Descriptor {}
80+ var uncompressed = "containerd.io/uncompressed"
7681
7782// NewWindowsDiff is the Windows container layer implementation
7883// for comparing and applying filesystem layers
@@ -135,6 +140,7 @@ func (s windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
135140 // TODO darrenstahlmsft: When this is done isolated, we should disable these.
136141 // it currently cannot be disabled, unless we add ref counting. Since this is
137142 // temporary, leaving it enabled is OK for now.
143+ // https://github.com/containerd/containerd/issues/1681
138144 if err := winio .EnableProcessPrivileges ([]string {winio .SeBackupPrivilege , winio .SeRestorePrivilege }); err != nil {
139145 return emptyDesc , err
140146 }
@@ -158,7 +164,136 @@ func (s windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
158164// Compare creates a diff between the given mounts and uploads the result
159165// to the content store.
160166func (s windowsDiff ) Compare (ctx context.Context , lower , upper []mount.Mount , opts ... diff.Opt ) (d ocispec.Descriptor , err error ) {
161- return emptyDesc , errors .Wrap (errdefs .ErrNotImplemented , "windowsDiff does not implement Compare method" )
167+ t1 := time .Now ()
168+
169+ var config diff.Config
170+ for _ , opt := range opts {
171+ if err := opt (& config ); err != nil {
172+ return emptyDesc , err
173+ }
174+ }
175+
176+ layers , err := mountPairToLayerStack (lower , upper )
177+ if err != nil {
178+ return emptyDesc , err
179+ }
180+
181+ if config .MediaType == "" {
182+ config .MediaType = ocispec .MediaTypeImageLayerGzip
183+ }
184+
185+ var isCompressed bool
186+ switch config .MediaType {
187+ case ocispec .MediaTypeImageLayer :
188+ case ocispec .MediaTypeImageLayerGzip :
189+ isCompressed = true
190+ default :
191+ return emptyDesc , errors .Wrapf (errdefs .ErrNotImplemented , "unsupported diff media type: %v" , config .MediaType )
192+ }
193+
194+ newReference := false
195+ if config .Reference == "" {
196+ newReference = true
197+ config .Reference = uniqueRef ()
198+ }
199+
200+ cw , err := s .store .Writer (ctx , content .WithRef (config .Reference ), content .WithDescriptor (ocispec.Descriptor {
201+ MediaType : config .MediaType ,
202+ }))
203+
204+ if err != nil {
205+ return emptyDesc , errors .Wrap (err , "failed to open writer" )
206+ }
207+
208+ defer func () {
209+ if err != nil {
210+ cw .Close ()
211+ if newReference {
212+ if abortErr := s .store .Abort (ctx , config .Reference ); abortErr != nil {
213+ log .G (ctx ).WithError (abortErr ).WithField ("ref" , config .Reference ).Warnf ("failed to delete diff upload" )
214+ }
215+ }
216+ }
217+ }()
218+
219+ if ! newReference {
220+ if err = cw .Truncate (0 ); err != nil {
221+ return emptyDesc , err
222+ }
223+ }
224+
225+ // TODO darrenstahlmsft: When this is done isolated, we should disable this.
226+ // it currently cannot be disabled, unless we add ref counting. Since this is
227+ // temporary, leaving it enabled is OK for now.
228+ // https://github.com/containerd/containerd/issues/1681
229+ if err := winio .EnableProcessPrivileges ([]string {winio .SeBackupPrivilege }); err != nil {
230+ return emptyDesc , err
231+ }
232+
233+ if isCompressed {
234+ dgstr := digest .SHA256 .Digester ()
235+ var compressed io.WriteCloser
236+ compressed , err = compression .CompressStream (cw , compression .Gzip )
237+ if err != nil {
238+ return emptyDesc , errors .Wrap (err , "failed to get compressed stream" )
239+ }
240+ err = archive .WriteDiff (ctx , io .MultiWriter (compressed , dgstr .Hash ()), "" , layers [0 ], archive .AsWindowsContainerLayerPair (), archive .WithParentLayers (layers [1 :]))
241+ compressed .Close ()
242+ if err != nil {
243+ return emptyDesc , errors .Wrap (err , "failed to write compressed diff" )
244+ }
245+
246+ if config .Labels == nil {
247+ config .Labels = map [string ]string {}
248+ }
249+ config .Labels [uncompressed ] = dgstr .Digest ().String ()
250+ } else {
251+ if err = archive .WriteDiff (ctx , cw , "" , layers [0 ], archive .AsWindowsContainerLayerPair (), archive .WithParentLayers (layers [1 :])); err != nil {
252+ return emptyDesc , errors .Wrap (err , "failed to write diff" )
253+ }
254+ }
255+
256+ var commitopts []content.Opt
257+ if config .Labels != nil {
258+ commitopts = append (commitopts , content .WithLabels (config .Labels ))
259+ }
260+
261+ dgst := cw .Digest ()
262+ if err := cw .Commit (ctx , 0 , dgst , commitopts ... ); err != nil {
263+ if ! errdefs .IsAlreadyExists (err ) {
264+ return emptyDesc , errors .Wrap (err , "failed to commit" )
265+ }
266+ }
267+
268+ info , err := s .store .Info (ctx , dgst )
269+ if err != nil {
270+ return emptyDesc , errors .Wrap (err , "failed to get info from content store" )
271+ }
272+ if info .Labels == nil {
273+ info .Labels = make (map [string ]string )
274+ }
275+ // Set uncompressed label if digest already existed without label
276+ if _ , ok := info .Labels [uncompressed ]; ! ok {
277+ info .Labels [uncompressed ] = config .Labels [uncompressed ]
278+ if _ , err := s .store .Update (ctx , info , "labels." + uncompressed ); err != nil {
279+ return emptyDesc , errors .Wrap (err , "error setting uncompressed label" )
280+ }
281+ }
282+
283+ desc := ocispec.Descriptor {
284+ MediaType : config .MediaType ,
285+ Size : info .Size ,
286+ Digest : info .Digest ,
287+ }
288+
289+ log .G (ctx ).WithFields (logrus.Fields {
290+ "d" : time .Since (t1 ),
291+ "dgst" : desc .Digest ,
292+ "size" : desc .Size ,
293+ "media" : desc .MediaType ,
294+ }).Debug ("diff created" )
295+
296+ return desc , nil
162297}
163298
164299type readCounter struct {
@@ -191,3 +326,58 @@ func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
191326
192327 return mnt .Source , parentLayerPaths , nil
193328}
329+
330+ // mountPairToLayerStack ensures that the two sets of mount-lists are actually a correct
331+ // parent-and-child, or orphan-and-empty-list, and return the full list of layers, starting
332+ // with the upper-most (most childish?)
333+ func mountPairToLayerStack (lower , upper []mount.Mount ) ([]string , error ) {
334+
335+ // May return an ErrNotImplemented, which will fall back to LCOW
336+ upperLayer , upperParentLayerPaths , err := mountsToLayerAndParents (upper )
337+ if err != nil {
338+ return nil , errors .Wrapf (err , "Upper mount invalid" )
339+ }
340+
341+ // Trivial case, diff-against-nothing
342+ if len (lower ) == 0 {
343+ if len (upperParentLayerPaths ) != 0 {
344+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a layer with parents against a null layer" )
345+ }
346+ return []string {upperLayer }, nil
347+ }
348+
349+ if len (upperParentLayerPaths ) < 1 {
350+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a layer with no parents against another layer" )
351+ }
352+
353+ lowerLayer , lowerParentLayerPaths , err := mountsToLayerAndParents (lower )
354+ if errdefs .IsNotImplemented (err ) {
355+ // Upper was a windows-layer, lower is not. We can't handle that.
356+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a windows-layer against a non-windows-layer" )
357+ } else if err != nil {
358+ return nil , errors .Wrapf (err , "Lower mount invalid" )
359+ }
360+
361+ if upperParentLayerPaths [0 ] != lowerLayer {
362+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a layer against a layer other than its own parent" )
363+ }
364+
365+ if len (upperParentLayerPaths ) != len (lowerParentLayerPaths )+ 1 {
366+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a layer against a layer with different parents" )
367+ }
368+ for i , upperParent := range upperParentLayerPaths [1 :] {
369+ if upperParent != lowerParentLayerPaths [i ] {
370+ return nil , errors .Wrap (errdefs .ErrInvalidArgument , "windowsDiff cannot diff a layer against a layer with different parents" )
371+ }
372+ }
373+
374+ return append ([]string {upperLayer }, upperParentLayerPaths ... ), nil
375+ }
376+
377+ func uniqueRef () string {
378+ t := time .Now ()
379+ var b [3 ]byte
380+ // Ignore read failures, just decreases uniqueness
381+ rand .Read (b [:])
382+ return fmt .Sprintf ("%d-%s" , t .UnixNano (), base64 .URLEncoding .EncodeToString (b [:]))
383+ }
0 commit comments