From c2186ef415f161fd5e9d683ff659f3f710033ac1 Mon Sep 17 00:00:00 2001 From: Eric Li Date: Tue, 14 Aug 2018 01:02:06 +0800 Subject: [PATCH] bugfix: populate volume error Signed-off-by: Eric Li --- daemon/mgr/container_storage.go | 12 +--- pkg/archive/archive.go | 116 ++++++++++++++++++++++++++++++++ pkg/archive/archive_test.go | 79 ++++++++++++++++++++++ pkg/utils/utils.go | 29 ++++++++ pkg/utils/utils_test.go | 21 ++++++ 5 files changed, 248 insertions(+), 9 deletions(-) create mode 100644 pkg/archive/archive.go create mode 100644 pkg/archive/archive_test.go diff --git a/daemon/mgr/container_storage.go b/daemon/mgr/container_storage.go index 9f4ed6e4c..eedbfcae3 100644 --- a/daemon/mgr/container_storage.go +++ b/daemon/mgr/container_storage.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path" "path/filepath" "regexp" @@ -13,6 +12,7 @@ import ( "github.com/alibaba/pouch/apis/opts" "github.com/alibaba/pouch/apis/types" + "github.com/alibaba/pouch/pkg/archive" "github.com/alibaba/pouch/pkg/errtypes" "github.com/alibaba/pouch/pkg/randomid" "github.com/alibaba/pouch/pkg/system" @@ -672,15 +672,9 @@ func copyImageContent(source, destination string) error { return err } if len(dstList) == 0 { - // TODO: refactor - - // If the source volume is empty, copies files from the root into the volume - commandLine := fmt.Sprintf("cp -r %s/* %s", source, destination) - - cmd := exec.Command("sh", "-c", commandLine) - out, err := cmd.CombinedOutput() + err := archive.CopyWithTar(source, destination) if err != nil { - logrus.Errorf("copyImageContent: %s, %v", string(out), err) + logrus.Errorf("copyImageContent: %v", err) return err } } diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go new file mode 100644 index 000000000..439197c5f --- /dev/null +++ b/pkg/archive/archive.go @@ -0,0 +1,116 @@ +package archive + +import ( + "archive/tar" + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +func tarFromDir(src string, writer io.Writer) error { + // ensure the src actually exists before trying to tar it + if _, err := os.Stat(src); err != nil { + return fmt.Errorf("failed to stat source file %s: %v", src, err) + } + + tw := tar.NewWriter(writer) + defer tw.Close() + + // walk path + return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + // create a new dir/file header + header, err := tar.FileInfoHeader(fi, fi.Name()) + if err != nil { + return err + } + + // update the name to correctly reflect the desired destination when untaring + header.Name = strings.TrimPrefix(strings.Replace(file, src, "", 1), string(filepath.Separator)) + // write the header + if err := tw.WriteHeader(header); err != nil { + return err + } + + if !fi.Mode().IsRegular() { + return nil + } + + // open files for taring + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() + + // copy file data into tar writer + if _, err := io.Copy(tw, f); err != nil { + return err + } + + return nil + }) +} + +func untarToDir(dst string, r io.Reader) error { + tr := tar.NewReader(r) + + for { + header, err := tr.Next() + + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case header == nil: + continue + } + + // the target location where the dir/file should be created + target := filepath.Join(dst, header.Name) + + // check the file type + switch header.Typeflag { + case tar.TypeDir: + if _, err := os.Stat(target); err != nil { + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + } + case tar.TypeReg: + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(f, tr); err != nil { + return err + } + } + } +} + +// CopyWithTar create a tar from src directory, +// and untar it in dst directory. +func CopyWithTar(src, dst string) error { + buf := new(bytes.Buffer) + + if err := tarFromDir(src, buf); err != nil { + return err + } + + if err := untarToDir(dst, buf); err != nil { + return err + } + + return nil + +} diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go new file mode 100644 index 000000000..fda52bb9c --- /dev/null +++ b/pkg/archive/archive_test.go @@ -0,0 +1,79 @@ +package archive + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/alibaba/pouch/pkg/utils" +) + +func TestCopyWithTar(t *testing.T) { + source, err := ioutil.TempDir("", "source") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(source) + + destination, err := ioutil.TempDir("", "destination") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(destination) + + files := []string{"file1", "file2", "dir1/file3", "dir2/file4"} + + err = makeFiles(source, files) + if err != nil { + t.Fatal(err) + } + + err = CopyWithTar(source, destination) + if err != nil { + t.Fatal(err) + } + actualFiles := targetFiles(destination) + + if !utils.StringSliceEqual(files, actualFiles) { + t.Fatalf(" TestCopyWithTar expected get %v, but got %v", files, actualFiles) + } +} + +func makeFiles(baseDir string, files []string) error { + for _, file := range files { + fullPath := path.Join(baseDir, file) + + dir := path.Dir(fullPath) + + // create dir. + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + // create file. + fi, err := os.Create(fullPath) + if err != nil { + return err + } + fi.Close() + } + + return nil +} + +func targetFiles(baseDir string) []string { + files := []string{} + + filepath.Walk(baseDir, func(name string, fi os.FileInfo, err error) error { + if fi.Mode().IsRegular() { + files = append(files, strings.TrimPrefix(name, baseDir+"/")) + } + return nil + }) + + return files +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index c8e45d863..fea388d4d 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -301,3 +301,32 @@ func IsFileExist(file string) bool { return false } + +// StringSliceEqual compare two string slice, ignore the order. +func StringSliceEqual(s1, s2 []string) bool { + if s1 == nil && s2 == nil { + return true + } + + if s1 == nil || s2 == nil { + return false + } + + if len(s1) != len(s2) { + return false + } + + for _, s := range s1 { + if !StringInSlice(s2, s) { + return false + } + } + + for _, s := range s2 { + if !StringInSlice(s1, s) { + return false + } + } + + return true +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 1c5797d83..942d0792b 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -523,3 +523,24 @@ func TestIsFileExist(t *testing.T) { assert.Equal(IsFileExist(t.path), t.exist) } } + +func TestStringSliceEqual(t *testing.T) { + tests := []struct { + s1 []string + s2 []string + equal bool + }{ + {nil, nil, true}, + {nil, []string{"a"}, false}, + {[]string{"a"}, []string{"a"}, true}, + {[]string{"a"}, []string{"b", "a"}, false}, + {[]string{"a", "b"}, []string{"b", "a"}, true}, + } + + for _, test := range tests { + result := StringSliceEqual(test.s1, test.s2) + if result != test.equal { + t.Fatalf("StringSliceEqual(%v, %v) expected: %v, but got %v", test.s1, test.s2, test.equal, result) + } + } +}