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