Support applying with parent directories
Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
parent
5a0ff41c81
commit
bcc4a146e4
159
archive/tar.go
159
archive/tar.go
@ -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 {
|
||||||
|
options.applyFunc = applyNaive
|
||||||
|
}
|
||||||
|
|
||||||
return apply(ctx, root, tar.NewReader(r), options)
|
return options.applyFunc(ctx, root, tar.NewReader(r), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyNaive applies a tar stream of an OCI style diff tar.
|
// 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)
|
return 0, err
|
||||||
if err != nil {
|
|
||||||
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
183
archive/tar_linux_test.go
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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 {
|
|
||||||
_, err := archive.Apply(ctx, root, r)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
||||||
|
_, err := archive.Apply(ctx, root, r)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAufsPath(options []string) (string, error) {
|
// 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
|
|
||||||
}
|
}
|
||||||
return "", errors.New("rw branch not found")
|
if upper == "" {
|
||||||
|
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "rw branch not found")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user