Support applying with parent directories

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan 2019-07-30 15:45:14 -07:00
parent 5a0ff41c81
commit bcc4a146e4
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
11 changed files with 483 additions and 160 deletions

View File

@ -110,11 +110,15 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int
if options.Filter == nil { if options.Filter == nil {
options.Filter = all options.Filter = all
} }
if options.applyFunc == nil {
return apply(ctx, root, tar.NewReader(r), options) options.applyFunc = applyNaive
} }
// applyNaive applies a tar stream of an OCI style diff tar. return options.applyFunc(ctx, root, tar.NewReader(r), options)
}
// applyNaive applies a tar stream of an OCI style diff tar to a directory
// applying each file as either a whole file or whiteout.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) { func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
var ( var (
@ -123,8 +127,50 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
// Used for handling opaque directory markers which // Used for handling opaque directory markers which
// may occur out of order // may occur out of order
unpackedPaths = make(map[string]struct{}) unpackedPaths = make(map[string]struct{})
convertWhiteout = options.ConvertWhiteout
) )
if convertWhiteout == nil {
// handle whiteouts by removing the target files
convertWhiteout = func(hdr *tar.Header, path string) (bool, error) {
base := filepath.Base(path)
dir := filepath.Dir(path)
if base == whiteoutOpaqueDir {
_, err := os.Lstat(dir)
if err != nil {
return false, err
}
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
if os.IsNotExist(err) {
err = nil // parent was deleted
}
return err
}
if path == dir {
return nil
}
if _, exists := unpackedPaths[path]; !exists {
err := os.RemoveAll(path)
return err
}
return nil
})
return false, err
}
if strings.HasPrefix(base, whiteoutPrefix) {
originalBase := base[len(whiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
return false, os.RemoveAll(originalPath)
}
return true, nil
}
}
// Iterate through the files in the archive. // Iterate through the files in the archive.
for { for {
select { select {
@ -182,55 +228,13 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
if base == "" { if base == "" {
parentPath = filepath.Dir(path) parentPath = filepath.Dir(path)
} }
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { if err := mkparent(ctx, parentPath, root, options.Parents); err != nil {
err = mkdirAll(parentPath, 0755)
if err != nil {
return 0, err return 0, err
} }
} }
}
// Naive whiteout convert function which handles whiteout files by // Naive whiteout convert function which handles whiteout files by
// removing the target files. // removing the target files.
convertWhiteout := func(hdr *tar.Header, path string) (bool, error) {
base := filepath.Base(path)
dir := filepath.Dir(path)
if base == whiteoutOpaqueDir {
_, err := os.Lstat(dir)
if err != nil {
return false, err
}
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
if os.IsNotExist(err) {
err = nil // parent was deleted
}
return err
}
if path == dir {
return nil
}
if _, exists := unpackedPaths[path]; !exists {
err := os.RemoveAll(path)
return err
}
return nil
})
return false, err
}
if strings.HasPrefix(base, whiteoutPrefix) {
originalBase := base[len(whiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
return false, os.RemoveAll(originalPath)
}
return true, nil
}
if options.ConvertWhiteout != nil {
convertWhiteout = options.ConvertWhiteout
}
if err := validateWhiteout(path); err != nil { if err := validateWhiteout(path); err != nil {
return 0, err return 0, err
} }
@ -375,6 +379,66 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)) return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
} }
func mkparent(ctx context.Context, path, root string, parents []string) error {
if dir, err := os.Lstat(path); err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{
Op: "mkparent",
Path: path,
Err: syscall.ENOTDIR,
}
} else if !os.IsNotExist(err) {
return err
}
i := len(path)
for i > len(root) && !os.IsPathSeparator(path[i-1]) {
i--
}
if i > len(root)+1 {
if err := mkparent(ctx, path[:i-1], root, parents); err != nil {
return err
}
}
if err := mkdir(path, 0755); err != nil {
// Check that still doesn't exist
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
for _, p := range parents {
ppath, err := fs.RootPath(p, path[len(root):])
if err != nil {
return err
}
dir, err := os.Lstat(ppath)
if err == nil {
if !dir.IsDir() {
// Replaced, do not copy attributes
break
}
if err := copyDirInfo(dir, path); err != nil {
return err
}
return copyUpXAttrs(path, ppath)
} else if !os.IsNotExist(err) {
return err
}
}
log.G(ctx).Debugf("parent directory %q not found: default permissions(0755) used", path)
return nil
}
type changeWriter struct { type changeWriter struct {
tw *tar.Writer tw *tar.Writer
source string source string
@ -545,6 +609,9 @@ func (cw *changeWriter) Close() error {
} }
func (cw *changeWriter) includeParents(hdr *tar.Header) error { func (cw *changeWriter) includeParents(hdr *tar.Header) error {
if cw.addedDirs == nil {
return nil
}
name := strings.TrimRight(hdr.Name, "/") name := strings.TrimRight(hdr.Name, "/")
fname := filepath.Join(cw.source, name) fname := filepath.Join(cw.source, name)
parent := filepath.Dir(name) parent := filepath.Dir(name)

183
archive/tar_linux_test.go Normal file
View File

@ -0,0 +1,183 @@
// +build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/containerd/containerd/log/logtest"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/pkg/testutil"
"github.com/containerd/containerd/snapshots/overlay"
"github.com/containerd/continuity/fs"
"github.com/containerd/continuity/fs/fstest"
"github.com/pkg/errors"
)
func TestOverlayApply(t *testing.T) {
testutil.RequiresRoot(t)
base, err := ioutil.TempDir("", "test-ovl-diff-apply-")
if err != nil {
t.Fatalf("unable to create temp dir: %+v", err)
}
defer os.RemoveAll(base)
if err := overlay.Supported(base); err != nil {
t.Skipf("skipping because overlay is not supported %v", err)
}
fstest.FSSuite(t, overlayDiffApplier{
tmp: base,
diff: WriteDiff,
t: t,
})
}
func TestOverlayApplyNoParents(t *testing.T) {
testutil.RequiresRoot(t)
base, err := ioutil.TempDir("", "test-ovl-diff-apply-")
if err != nil {
t.Fatalf("unable to create temp dir: %+v", err)
}
defer os.RemoveAll(base)
if err := overlay.Supported(base); err != nil {
t.Skipf("skipping because overlay is not supported %v", err)
}
fstest.FSSuite(t, overlayDiffApplier{
tmp: base,
diff: func(ctx context.Context, w io.Writer, a, b string) error {
cw := newChangeWriter(w, b)
cw.addedDirs = nil
err := fs.Changes(ctx, a, b, cw.HandleChange)
if err != nil {
return errors.Wrap(err, "failed to create diff tar stream")
}
return cw.Close()
},
t: t,
})
}
type overlayDiffApplier struct {
tmp string
diff func(context.Context, io.Writer, string, string) error
t *testing.T
}
type overlayContext struct {
merged string
lowers []string
mounted bool
}
type contextKey struct{}
func (d overlayDiffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
merged, err := ioutil.TempDir(d.tmp, "merged")
if err != nil {
return ctx, nil, errors.Wrap(err, "failed to make merged dir")
}
oc := &overlayContext{
merged: merged,
}
ctx = logtest.WithT(ctx, d.t)
return context.WithValue(ctx, contextKey{}, oc), func() {
if oc.mounted {
mount.Unmount(oc.merged, 0)
}
}, nil
}
func (d overlayDiffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) {
oc := ctx.Value(contextKey{}).(*overlayContext)
applyCopy, err := ioutil.TempDir(d.tmp, "apply-copy-")
if err != nil {
return "", nil, errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(applyCopy)
base := oc.merged
if len(oc.lowers) == 1 {
base = oc.lowers[0]
}
if err = fs.CopyDir(applyCopy, base); err != nil {
return "", nil, errors.Wrap(err, "failed to copy base")
}
if err := a.Apply(applyCopy); err != nil {
return "", nil, errors.Wrap(err, "failed to apply changes to copy of base")
}
buf := bytes.NewBuffer(nil)
if err := d.diff(ctx, buf, base, applyCopy); err != nil {
return "", nil, errors.Wrap(err, "failed to create diff")
}
if oc.mounted {
if err := mount.Unmount(oc.merged, 0); err != nil {
return "", nil, errors.Wrap(err, "failed to unmount")
}
oc.mounted = false
}
next, err := ioutil.TempDir(d.tmp, "lower-")
if err != nil {
return "", nil, errors.Wrap(err, "failed to create temp dir")
}
if _, err = Apply(ctx, next, buf, WithConvertWhiteout(OverlayConvertWhiteout), WithParents(oc.lowers)); err != nil {
return "", nil, errors.Wrap(err, "failed to apply tar stream")
}
oc.lowers = append([]string{next}, oc.lowers...)
if len(oc.lowers) == 1 {
return oc.lowers[0], nil, nil
}
m := mount.Mount{
Type: "overlay",
Source: "overlay",
Options: []string{
fmt.Sprintf("lowerdir=%s", strings.Join(oc.lowers, ":")),
},
}
if err := m.Mount(oc.merged); err != nil {
return "", nil, errors.Wrapf(err, "failed to mount: %v", m)
}
oc.mounted = true
return oc.merged, nil, nil
}

View File

@ -16,7 +16,19 @@
package archive package archive
import "archive/tar" import (
"archive/tar"
"context"
)
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
Filter Filter // Filter tar headers
ConvertWhiteout ConvertWhiteout // Convert whiteout files
Parents []string // Parent directories to handle inherited attributes without CoW
applyFunc func(context.Context, string, *tar.Reader, ApplyOptions) (int64, error)
}
// ApplyOpt allows setting mutable archive apply properties on creation // ApplyOpt allows setting mutable archive apply properties on creation
type ApplyOpt func(options *ApplyOptions) error type ApplyOpt func(options *ApplyOptions) error
@ -47,3 +59,16 @@ func WithConvertWhiteout(c ConvertWhiteout) ApplyOpt {
return nil return nil
} }
} }
// WithParents provides parent directories for resolving inherited attributes
// directory from the filesystem.
// Inherited attributes are searched from first to last, making the first
// element in the list the most immediate parent directory.
// NOTE: When applying to a filesystem which supports CoW, file attributes
// should be inherited by the filesystem.
func WithParents(p []string) ApplyOpt {
return func(options *ApplyOptions) error {
options.Parents = p
return nil
}
}

View File

@ -27,12 +27,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
Filter Filter // Filter tar headers
ConvertWhiteout ConvertWhiteout // Convert whiteout files
}
// AufsConvertWhiteout converts whiteout files for aufs. // AufsConvertWhiteout converts whiteout files for aufs.
func AufsConvertWhiteout(_ *tar.Header, _ string) (bool, error) { func AufsConvertWhiteout(_ *tar.Header, _ string) (bool, error) {
return true, nil return true, nil

View File

@ -1,25 +0,0 @@
// +build !linux,!windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
Filter Filter // Filter tar headers
ConvertWhiteout ConvertWhiteout // Convert whiteout files
}

View File

@ -18,29 +18,12 @@
package archive package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
ParentLayerPaths []string // Parent layer paths used for Windows layer apply
IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer
Filter Filter // Filter tar headers
ConvertWhiteout ConvertWhiteout // Convert whiteout files
}
// WithParentLayers adds parent layers to the apply process this is required
// for all Windows layers except the base layer.
func WithParentLayers(parentPaths []string) ApplyOpt {
return func(options *ApplyOptions) error {
options.ParentLayerPaths = parentPaths
return nil
}
}
// AsWindowsContainerLayer indicates that the tar stream to apply is that of // AsWindowsContainerLayer indicates that the tar stream to apply is that of
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and // a Windows Container Layer. The caller must be holding SeBackupPrivilege and
// SeRestorePrivilege. // SeRestorePrivilege.
func AsWindowsContainerLayer() ApplyOpt { func AsWindowsContainerLayer() ApplyOpt {
return func(options *ApplyOptions) error { return func(options *ApplyOptions) error {
options.IsWindowsContainerLayer = true options.applyFunc = applyWindowsLayer
return nil return nil
} }
} }

View File

@ -20,11 +20,12 @@ package archive
import ( import (
"archive/tar" "archive/tar"
"context"
"os" "os"
"strings"
"sync" "sync"
"syscall" "syscall"
"github.com/containerd/continuity/fs"
"github.com/containerd/continuity/sysx" "github.com/containerd/continuity/sysx"
"github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/system"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -74,10 +75,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
return f, err return f, err
} }
func mkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
func mkdir(path string, perm os.FileMode) error { func mkdir(path string, perm os.FileMode) error {
if err := os.Mkdir(path, perm); err != nil { if err := os.Mkdir(path, perm); err != nil {
return err return err
@ -149,11 +146,71 @@ func getxattr(path, attr string) ([]byte, error) {
} }
func setxattr(path, key, value string) error { func setxattr(path, key, value string) error {
return sysx.LSetxattr(path, key, []byte(value), 0) // Do not set trusted attributes
if strings.HasPrefix(key, "trusted.") {
return errors.Wrap(unix.ENOTSUP, "admin attributes from archive not supported")
}
return unix.Lsetxattr(path, key, []byte(value), 0)
} }
// apply applies a tar stream of an OCI style diff tar. func copyDirInfo(fi os.FileInfo, path string) error {
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets st := fi.Sys().(*syscall.Stat_t)
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) { if err := os.Lchown(path, int(st.Uid), int(st.Gid)); err != nil {
return applyNaive(ctx, root, tr, options) if os.IsPermission(err) {
// Normally if uid/gid are the same this would be a no-op, but some
// filesystems may still return EPERM... for instance NFS does this.
// In such a case, this is not an error.
if dstStat, err2 := os.Lstat(path); err2 == nil {
st2 := dstStat.Sys().(*syscall.Stat_t)
if st.Uid == st2.Uid && st.Gid == st2.Gid {
err = nil
}
}
}
if err != nil {
return errors.Wrapf(err, "failed to chown %s", path)
}
}
if err := os.Chmod(path, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", path)
}
timespec := []unix.Timespec{unix.Timespec(fs.StatAtime(st)), unix.Timespec(fs.StatMtime(st))}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return errors.Wrapf(err, "failed to utime %s", path)
}
return nil
}
func copyUpXAttrs(dst, src string) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
if err == unix.ENOTSUP || err == sysx.ENODATA {
return nil
}
return errors.Wrapf(err, "failed to list xattrs on %s", src)
}
for _, xattr := range xattrKeys {
// Do not copy up trusted attributes
if strings.HasPrefix(xattr, "trusted.") {
continue
}
data, err := sysx.LGetxattr(src, xattr)
if err != nil {
if err == unix.ENOTSUP || err == sysx.ENODATA {
continue
}
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
}
if err := unix.Lsetxattr(dst, xattr, data, unix.XATTR_CREATE); err != nil {
if err == unix.ENOTSUP || err == unix.ENODATA || err == unix.EEXIST {
continue
}
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
}
}
return nil
} }

View File

@ -23,7 +23,6 @@ import (
"bufio" "bufio"
"context" "context"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -36,6 +35,7 @@ import (
"github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim" "github.com/Microsoft/hcsshim"
"github.com/containerd/containerd/sys" "github.com/containerd/containerd/sys"
"github.com/pkg/errors"
) )
const ( const (
@ -107,10 +107,6 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
return sys.OpenFileSequential(name, flag, perm) return sys.OpenFileSequential(name, flag, perm)
} }
func mkdirAll(path string, perm os.FileMode) error {
return sys.MkdirAll(path, perm)
}
func mkdir(path string, perm os.FileMode) error { func mkdir(path string, perm os.FileMode) error {
return os.Mkdir(path, perm) return os.Mkdir(path, perm)
} }
@ -153,16 +149,8 @@ func setxattr(path, key, value string) error {
return errors.New("xattrs not supported on Windows") return errors.New("xattrs not supported on Windows")
} }
// apply applies a tar stream of an OCI style diff tar of a Windows layer. // applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets // layer using the hcsshim layer writer and backup streams.
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
if options.IsWindowsContainerLayer {
return applyWindowsLayer(ctx, root, tr, options)
}
return applyNaive(ctx, root, tr, options)
}
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows layer.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) { func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
home, id := filepath.Split(root) home, id := filepath.Split(root)
@ -170,7 +158,7 @@ func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options
HomeDir: home, HomeDir: home,
} }
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths) w, err := hcsshim.NewLayerWriter(info, id, options.Parents)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -443,3 +431,14 @@ func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
} }
} }
} }
func copyDirInfo(fi os.FileInfo, path string) error {
if err := os.Chmod(path, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", path)
}
return nil
}
func copyUpXAttrs(dst, src string) error {
return nil
}

View File

@ -24,6 +24,7 @@ import (
"strings" "strings"
"github.com/containerd/containerd/archive" "github.com/containerd/containerd/archive"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -31,60 +32,97 @@ import (
func apply(ctx context.Context, mounts []mount.Mount, r io.Reader) error { func apply(ctx context.Context, mounts []mount.Mount, r io.Reader) error {
switch { switch {
case len(mounts) == 1 && mounts[0].Type == "overlay": case len(mounts) == 1 && mounts[0].Type == "overlay":
path, err := getOverlayPath(mounts[0].Options) path, parents, err := getOverlayPath(mounts[0].Options)
if err != nil { if err != nil {
if errdefs.IsInvalidArgument(err) {
break
}
return err return err
} }
_, err = archive.Apply(ctx, path, r, opts := []archive.ApplyOpt{
archive.WithConvertWhiteout(archive.OverlayConvertWhiteout)) archive.WithConvertWhiteout(archive.OverlayConvertWhiteout),
}
if len(parents) > 0 {
opts = append(opts, archive.WithParents(parents))
}
_, err = archive.Apply(ctx, path, r, opts...)
return err return err
case len(mounts) == 1 && mounts[0].Type == "aufs": case len(mounts) == 1 && mounts[0].Type == "aufs":
path, err := getAufsPath(mounts[0].Options) path, parents, err := getAufsPath(mounts[0].Options)
if err != nil { if err != nil {
if errdefs.IsInvalidArgument(err) {
break
}
return err return err
} }
_, err = archive.Apply(ctx, path, r, opts := []archive.ApplyOpt{
archive.WithConvertWhiteout(archive.AufsConvertWhiteout)) archive.WithConvertWhiteout(archive.AufsConvertWhiteout),
}
if len(parents) > 0 {
opts = append(opts, archive.WithParents(parents))
}
_, err = archive.Apply(ctx, path, r, opts...)
return err return err
default: }
return mount.WithTempMount(ctx, mounts, func(root string) error { return mount.WithTempMount(ctx, mounts, func(root string) error {
_, err := archive.Apply(ctx, root, r) _, err := archive.Apply(ctx, root, r)
return err return err
}) })
} }
}
func getOverlayPath(options []string) (string, error) { func getOverlayPath(options []string) (upper string, lower []string, err error) {
const upperdirPrefix = "upperdir=" const upperdirPrefix = "upperdir="
const lowerdirPrefix = "lowerdir="
for _, o := range options { for _, o := range options {
if strings.HasPrefix(o, upperdirPrefix) { if strings.HasPrefix(o, upperdirPrefix) {
return strings.TrimPrefix(o, upperdirPrefix), nil upper = strings.TrimPrefix(o, upperdirPrefix)
} else if strings.HasPrefix(o, lowerdirPrefix) {
lower = strings.Split(strings.TrimPrefix(o, lowerdirPrefix), ":")
} }
} }
return "", errors.New("upperdir not found") if upper == "" {
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "upperdir not found")
} }
func getAufsPath(options []string) (string, error) { return
}
// getAufsPath handles options as given by the containerd aufs package only,
// formatted as "br:<upper>=rw[:<lower>=ro+wh]*"
func getAufsPath(options []string) (upper string, lower []string, err error) {
const ( const (
sep = ":" sep = ":"
brPrefix1 = "br:" brPrefix = "br:"
brPrefix2 = "br="
rwSuffix = "=rw" rwSuffix = "=rw"
roSuffix = "=ro+wh"
) )
for _, o := range options { for _, o := range options {
if strings.HasPrefix(o, brPrefix1) { if strings.HasPrefix(o, brPrefix) {
o = strings.TrimPrefix(o, brPrefix1) o = strings.TrimPrefix(o, brPrefix)
} else if strings.HasPrefix(o, brPrefix2) {
o = strings.TrimPrefix(o, brPrefix2)
} else { } else {
continue continue
} }
for _, b := range strings.Split(o, sep) { for _, b := range strings.Split(o, sep) {
if strings.HasSuffix(b, rwSuffix) { if strings.HasSuffix(b, rwSuffix) {
return strings.TrimSuffix(b, rwSuffix), nil if upper != "" {
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "multiple rw branch found")
}
upper = strings.TrimSuffix(b, rwSuffix)
} else if strings.HasSuffix(b, roSuffix) {
if upper == "" {
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "rw branch be first")
}
lower = append(lower, strings.TrimSuffix(b, roSuffix))
} else {
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "unhandled aufs suffix")
}
} }
} }
break if upper == "" {
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "rw branch not found")
} }
return "", errors.New("rw branch not found") return
} }

View File

@ -23,34 +23,32 @@ import (
) )
func TestGetOverlayPath(t *testing.T) { func TestGetOverlayPath(t *testing.T) {
good := []string{"upperdir=/test/upper", "lowerdir=/test/lower", "workdir=/test/work"} good := []string{"upperdir=/test/upper", "lowerdir=/test/lower1:/test/lower2", "workdir=/test/work"}
path, err := getOverlayPath(good) path, parents, err := getOverlayPath(good)
if err != nil { if err != nil {
t.Fatalf("Get overlay path failed: %v", err) t.Fatalf("Get overlay path failed: %v", err)
} }
if path != "/test/upper" { if path != "/test/upper" {
t.Fatalf("Unexpected upperdir: %q", path) t.Fatalf("Unexpected upperdir: %q", path)
} }
if len(parents) != 2 || parents[0] != "/test/lower1" || parents[1] != "/test/lower2" {
t.Fatalf("Unexpected parents: %v", parents)
}
bad := []string{"lowerdir=/test/lower"} bad := []string{"lowerdir=/test/lower"}
_, err = getOverlayPath(bad) _, _, err = getOverlayPath(bad)
if err == nil { if err == nil {
t.Fatalf("An error is expected") t.Fatalf("An error is expected")
} }
} }
func TestGetAufsPath(t *testing.T) { func TestGetAufsPath(t *testing.T) {
rwDir := "/test/rw"
for _, test := range []struct { for _, test := range []struct {
options []string options []string
expectErr bool expectErr bool
}{ }{
{ {
options: []string{"random:option", "br:" + rwDir + "=rw:/test/ro=ro+wh"}, options: []string{"random:option", "br:/test/rw=rw:/test/ro=ro+wh"},
expectErr: false,
},
{
options: []string{"random:option", "br=" + rwDir + "=rw:/test/ro=ro+wh"},
expectErr: false, expectErr: false,
}, },
{ {
@ -62,7 +60,7 @@ func TestGetAufsPath(t *testing.T) {
expectErr: true, expectErr: true,
}, },
} { } {
path, err := getAufsPath(test.options) path, parents, err := getAufsPath(test.options)
if test.expectErr { if test.expectErr {
if err == nil { if err == nil {
t.Fatalf("An error is expected") t.Fatalf("An error is expected")
@ -72,8 +70,12 @@ func TestGetAufsPath(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Get aufs path failed: %v", err) t.Fatalf("Get aufs path failed: %v", err)
} }
if path != rwDir { if path != "/test/rw" {
t.Fatalf("Unexpected rw dir: %q", path) t.Fatalf("Unexpected rw dir: %q", path)
} }
if len(parents) != 1 || parents[0] != "/test/ro" {
t.Fatalf("Unexpected parents: %v", parents)
}
} }
} }

View File

@ -139,7 +139,7 @@ func (s windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
return emptyDesc, err return emptyDesc, err
} }
if _, err := archive.Apply(ctx, layer, rc, archive.WithParentLayers(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil { if _, err := archive.Apply(ctx, layer, rc, archive.WithParents(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil {
return emptyDesc, err return emptyDesc, err
} }