diff --git a/pkg/kubectl/cmd/cp.go b/pkg/kubectl/cmd/cp.go index 18545006a18..8b3a89096b6 100644 --- a/pkg/kubectl/cmd/cp.go +++ b/pkg/kubectl/cmd/cp.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "errors" + "fmt" "io" "io/ioutil" "os" @@ -82,7 +83,10 @@ type fileSpec struct { File string } -var errFileSpecDoesntMatchFormat = errors.New("Filespec must match the canonical format: [[namespace/]pod:]file/path") +var ( + errFileSpecDoesntMatchFormat = errors.New("Filespec must match the canonical format: [[namespace/]pod:]file/path") + errFileCannotBeEmpty = errors.New("Filepath can not be empty") +) func extractFileSpec(arg string) (fileSpec, error) { pieces := strings.Split(arg, ":") @@ -157,6 +161,9 @@ func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) } func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error { + if len(src.File) == 0 { + return errFileCannotBeEmpty + } reader, writer := io.Pipe() // strip trailing slash (if any) @@ -201,6 +208,10 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, } func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, dest fileSpec) error { + if len(src.File) == 0 { + return errFileCannotBeEmpty + } + reader, outStream := io.Pipe() options := &ExecOptions{ StreamOptions: StreamOptions{ @@ -222,7 +233,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d execute(f, cmd, options) }() prefix := getPrefix(src.File) - + prefix = path.Clean(prefix) return untarAll(reader, dest.File, prefix) } @@ -238,7 +249,7 @@ func makeTar(srcPath, destPath string, writer io.Writer) error { func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error { filepath := path.Join(srcBase, srcFile) - stat, err := os.Stat(filepath) + stat, err := os.Lstat(filepath) if err != nil { return err } @@ -247,28 +258,55 @@ func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) e if err != nil { return err } + if len(files) == 0 { + //case empty directory + hdr, _ := tar.FileInfoHeader(stat, filepath) + hdr.Name = destFile + if err := tw.WriteHeader(hdr); err != nil { + return err + } + } for _, f := range files { if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil { return err } } return nil - } - hdr, err := tar.FileInfoHeader(stat, filepath) - if err != nil { + } else if stat.Mode()&os.ModeSymlink != 0 { + //case soft link + hdr, _ := tar.FileInfoHeader(stat, filepath) + target, err := os.Readlink(filepath) + if err != nil { + return err + } + + hdr.Linkname = target + hdr.Name = destFile + if err := tw.WriteHeader(hdr); err != nil { + return err + } + } else { + //case regular file or other file type like pipe + hdr, err := tar.FileInfoHeader(stat, filepath) + if err != nil { + return err + } + hdr.Name = destFile + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + f, err := os.Open(filepath) + if err != nil { + return err + } + + defer f.Close() + _, err = io.Copy(tw, f) return err } - hdr.Name = destFile - if err := tw.WriteHeader(hdr); err != nil { - return err - } - f, err := os.Open(filepath) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(tw, f) - return err + return nil } func untarAll(reader io.Reader, destFile, prefix string) error { @@ -285,6 +323,7 @@ func untarAll(reader io.Reader, destFile, prefix string) error { break } entrySeq++ + mode := header.FileInfo().Mode() outFileName := path.Join(destFile, header.Name[len(prefix):]) baseName := path.Dir(outFileName) if err := os.MkdirAll(baseName, 0755); err != nil { @@ -308,15 +347,27 @@ func untarAll(reader io.Reader, destFile, prefix string) error { outFileName = filepath.Join(outFileName, path.Base(header.Name)) } } - outFile, err := os.Create(outFileName) - if err != nil { - return err - } - defer outFile.Close() - if _, err := io.Copy(outFile, tarReader); err != nil { - return err + + if mode&os.ModeSymlink != 0 { + err := os.Symlink(header.Linkname, outFileName) + if err != nil { + return err + } + } else { + outFile, err := os.Create(outFileName) + if err != nil { + return err + } + defer outFile.Close() + io.Copy(outFile, tarReader) } } + + if entrySeq == -1 { + //if no file was copied + errInfo := fmt.Sprintf("error: %s no such file or directory", prefix) + return errors.New(errInfo) + } return nil } diff --git a/pkg/kubectl/cmd/cp_test.go b/pkg/kubectl/cmd/cp_test.go index 4b8418d979a..b410bc694ba 100644 --- a/pkg/kubectl/cmd/cp_test.go +++ b/pkg/kubectl/cmd/cp_test.go @@ -29,6 +29,13 @@ import ( "testing" ) +type FileType int + +const ( + RegularFile FileType = 0 + SymLink FileType = 1 +) + func TestExtractFileSpec(t *testing.T) { tests := []struct { spec string @@ -119,24 +126,34 @@ func TestTarUntar(t *testing.T) { }() files := []struct { - name string - data string + name string + data string + fileType FileType }{ { - name: "foo", - data: "foobarbaz", + name: "foo", + data: "foobarbaz", + fileType: RegularFile, }, { - name: "dir/blah", - data: "bazblahfoo", + name: "dir/blah", + data: "bazblahfoo", + fileType: RegularFile, }, { - name: "some/other/directory/", - data: "with more data here", + name: "some/other/directory/", + data: "with more data here", + fileType: RegularFile, }, { - name: "blah", - data: "same file name different data", + name: "blah", + data: "same file name different data", + fileType: RegularFile, + }, + { + name: "gakki", + data: "/tmp/gakki", + fileType: SymLink, }, } @@ -146,20 +163,32 @@ func TestTarUntar(t *testing.T) { t.Errorf("unexpected error: %v", err) t.FailNow() } - f, err := os.Create(filepath) - if err != nil { - t.Errorf("unexpected error: %v", err) - t.FailNow() - } - defer f.Close() - if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil { - t.Errorf("unexpected error: %v", err) + if file.fileType == RegularFile { + f, err := os.Create(filepath) + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + defer f.Close() + if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + } else if file.fileType == SymLink { + err := os.Symlink(file.data, filepath) + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + } else { + t.Errorf("unexpected file type: %v", file) t.FailNow() } + } writer := &bytes.Buffer{} - if err := makeTar(dir, dir2, writer); err != nil { + if err := makeTar(dir, dir, writer); err != nil { t.Errorf("unexpected error: %v", err) } @@ -170,16 +199,35 @@ func TestTarUntar(t *testing.T) { } for _, file := range files { - filepath := path.Join(dir, file.name) - f, err := os.Open(filepath) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - defer f.Close() - buff := &bytes.Buffer{} - io.Copy(buff, f) - if file.data != string(buff.Bytes()) { - t.Errorf("expected: %s, saw: %s", file.data, string(buff.Bytes())) + absPath := dir2 + strings.TrimPrefix(dir, os.TempDir()) + filepath := path.Join(absPath, file.name) + + if file.fileType == RegularFile { + f, err := os.Open(filepath) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + defer f.Close() + buff := &bytes.Buffer{} + io.Copy(buff, f) + + if file.data != string(buff.Bytes()) { + t.Errorf("expected: %s, saw: %s", file.data, string(buff.Bytes())) + } + } else if file.fileType == SymLink { + dest, err := os.Readlink(filepath) + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if file.data != dest { + t.Errorf("expected: %s, saw: %s", file.data, dest) + } + } else { + t.Errorf("unexpected file type: %v", file) + t.FailNow() } } }