github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/diff/lcow/lcow.go (about)

     1  // +build windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package lcow
    20  
    21  import (
    22  	"context"
    23  	"io"
    24  	"os"
    25  	"path"
    26  	"time"
    27  
    28  	"github.com/Microsoft/go-winio/pkg/security"
    29  	"github.com/Microsoft/hcsshim/ext4/tar2ext4"
    30  	"github.com/containerd/containerd/content"
    31  	"github.com/containerd/containerd/diff"
    32  	"github.com/containerd/containerd/errdefs"
    33  	"github.com/containerd/containerd/log"
    34  	"github.com/containerd/containerd/metadata"
    35  	"github.com/containerd/containerd/mount"
    36  	"github.com/containerd/containerd/plugin"
    37  	digest "github.com/opencontainers/go-digest"
    38  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    39  	"github.com/pkg/errors"
    40  	"github.com/sirupsen/logrus"
    41  )
    42  
    43  const (
    44  	// maxLcowVhdSizeGB is the max size in GB of any layer
    45  	maxLcowVhdSizeGB = 128 * 1024 * 1024 * 1024
    46  )
    47  
    48  func init() {
    49  	plugin.Register(&plugin.Registration{
    50  		Type: plugin.DiffPlugin,
    51  		ID:   "windows-lcow",
    52  		Requires: []plugin.Type{
    53  			plugin.MetadataPlugin,
    54  		},
    55  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    56  			md, err := ic.Get(plugin.MetadataPlugin)
    57  			if err != nil {
    58  				return nil, err
    59  			}
    60  
    61  			ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{
    62  				OS:           "linux",
    63  				Architecture: "amd64",
    64  			})
    65  			return NewWindowsLcowDiff(md.(*metadata.DB).ContentStore())
    66  		},
    67  	})
    68  }
    69  
    70  // CompareApplier handles both comparison and
    71  // application of layer diffs.
    72  type CompareApplier interface {
    73  	diff.Applier
    74  	diff.Comparer
    75  }
    76  
    77  // windowsLcowDiff does filesystem comparison and application
    78  // for Windows specific Linux layer diffs.
    79  type windowsLcowDiff struct {
    80  	store content.Store
    81  }
    82  
    83  var emptyDesc = ocispec.Descriptor{}
    84  
    85  // NewWindowsLcowDiff is the Windows LCOW container layer implementation
    86  // for comparing and applying Linux filesystem layers on Windows
    87  func NewWindowsLcowDiff(store content.Store) (CompareApplier, error) {
    88  	return windowsLcowDiff{
    89  		store: store,
    90  	}, nil
    91  }
    92  
    93  // Apply applies the content associated with the provided digests onto the
    94  // provided mounts. Archive content will be extracted and decompressed if
    95  // necessary.
    96  func (s windowsLcowDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) {
    97  	t1 := time.Now()
    98  	defer func() {
    99  		if err == nil {
   100  			log.G(ctx).WithFields(logrus.Fields{
   101  				"d":     time.Since(t1),
   102  				"dgst":  desc.Digest,
   103  				"size":  desc.Size,
   104  				"media": desc.MediaType,
   105  			}).Debugf("diff applied")
   106  		}
   107  	}()
   108  
   109  	var config diff.ApplyConfig
   110  	for _, o := range opts {
   111  		if err := o(ctx, desc, &config); err != nil {
   112  			return emptyDesc, errors.Wrap(err, "failed to apply config opt")
   113  		}
   114  	}
   115  
   116  	layer, _, err := mountsToLayerAndParents(mounts)
   117  	if err != nil {
   118  		return emptyDesc, err
   119  	}
   120  
   121  	ra, err := s.store.ReaderAt(ctx, desc)
   122  	if err != nil {
   123  		return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
   124  	}
   125  	defer ra.Close()
   126  
   127  	processor := diff.NewProcessorChain(desc.MediaType, content.NewReader(ra))
   128  	for {
   129  		if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil {
   130  			return emptyDesc, errors.Wrapf(err, "failed to get stream processor for %s", desc.MediaType)
   131  		}
   132  		if processor.MediaType() == ocispec.MediaTypeImageLayer {
   133  			break
   134  		}
   135  	}
   136  	defer processor.Close()
   137  
   138  	// Calculate the Digest as we go
   139  	digester := digest.Canonical.Digester()
   140  	rc := &readCounter{
   141  		r: io.TeeReader(processor, digester.Hash()),
   142  	}
   143  
   144  	layerPath := path.Join(layer, "layer.vhd")
   145  	outFile, err := os.Create(layerPath)
   146  	if err != nil {
   147  		return emptyDesc, err
   148  	}
   149  	defer func() {
   150  		if err != nil {
   151  			outFile.Close()
   152  			os.Remove(layerPath)
   153  		}
   154  	}()
   155  
   156  	err = tar2ext4.Convert(rc, outFile, tar2ext4.ConvertWhiteout, tar2ext4.AppendVhdFooter, tar2ext4.MaximumDiskSize(maxLcowVhdSizeGB))
   157  	if err != nil {
   158  		return emptyDesc, errors.Wrapf(err, "failed to convert tar2ext4 vhd")
   159  	}
   160  	err = outFile.Sync()
   161  	if err != nil {
   162  		return emptyDesc, errors.Wrapf(err, "failed to sync tar2ext4 vhd to disk")
   163  	}
   164  	outFile.Close()
   165  
   166  	err = security.GrantVmGroupAccess(layerPath)
   167  	if err != nil {
   168  		return emptyDesc, errors.Wrapf(err, "failed GrantVmGroupAccess on layer vhd: %v", layerPath)
   169  	}
   170  
   171  	return ocispec.Descriptor{
   172  		MediaType: ocispec.MediaTypeImageLayer,
   173  		Size:      rc.c,
   174  		Digest:    digester.Digest(),
   175  	}, nil
   176  }
   177  
   178  // Compare creates a diff between the given mounts and uploads the result
   179  // to the content store.
   180  func (s windowsLcowDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) {
   181  	return emptyDesc, errors.Wrap(errdefs.ErrNotImplemented, "windowsLcowDiff does not implement Compare method")
   182  }
   183  
   184  type readCounter struct {
   185  	r io.Reader
   186  	c int64
   187  }
   188  
   189  func (rc *readCounter) Read(p []byte) (n int, err error) {
   190  	n, err = rc.r.Read(p)
   191  	rc.c += int64(n)
   192  	return
   193  }
   194  
   195  func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
   196  	if len(mounts) != 1 {
   197  		return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows lcow-layers")
   198  	}
   199  	mnt := mounts[0]
   200  	if mnt.Type != "lcow-layer" {
   201  		return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "mount layer type must be lcow-layer")
   202  	}
   203  
   204  	parentLayerPaths, err := mnt.GetParentPaths()
   205  	if err != nil {
   206  		return "", nil, err
   207  	}
   208  
   209  	return mnt.Source, parentLayerPaths, nil
   210  }