Commit 021e3ebf authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #52465 from WanLinghao/kubectl_cp_amend

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. improve kubectl cp command in several ways **Release note**: "kubectl cp" process soft link in better ways as well as some little bugs **Soft link**: before this patch "kubectl cp" command will copy the soft link to destination as an empty regular file after this patch "kubectl cp" command will behave the same as tar command this patch improves it on both from container and to container **some bugs** 1.from container to host a.when copy a file ends with '/', it will cause a panic. for example, container gakki has a regular file /tmp/test, then run command _kubectl cp gakki:/tmp/test/ /tmp_ a panic happens b.when copy a file which does not exist in container, the command ends up without any error information 2.from host to container a.when run command like kubectl cp "" gakki:/tmp it will try cp current directory to container, in other words, this command works the same as kubectl cp . gakki:/tmp b.current cp command will omit an empty directory
parents 02f0d921 b1f85e2d
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
...@@ -82,7 +83,10 @@ type fileSpec struct { ...@@ -82,7 +83,10 @@ type fileSpec struct {
File string 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) { func extractFileSpec(arg string) (fileSpec, error) {
pieces := strings.Split(arg, ":") pieces := strings.Split(arg, ":")
...@@ -157,6 +161,9 @@ func checkDestinationIsDir(dest fileSpec, f cmdutil.Factory, cmd *cobra.Command) ...@@ -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 { 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() reader, writer := io.Pipe()
// strip trailing slash (if any) // strip trailing slash (if any)
...@@ -201,6 +208,10 @@ func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, ...@@ -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 { 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() reader, outStream := io.Pipe()
options := &ExecOptions{ options := &ExecOptions{
StreamOptions: StreamOptions{ StreamOptions: StreamOptions{
...@@ -222,7 +233,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d ...@@ -222,7 +233,7 @@ func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, cmderr io.Writer, src, d
execute(f, cmd, options) execute(f, cmd, options)
}() }()
prefix := getPrefix(src.File) prefix := getPrefix(src.File)
prefix = path.Clean(prefix)
return untarAll(reader, dest.File, prefix) return untarAll(reader, dest.File, prefix)
} }
...@@ -238,7 +249,7 @@ func makeTar(srcPath, destPath string, writer io.Writer) error { ...@@ -238,7 +249,7 @@ func makeTar(srcPath, destPath string, writer io.Writer) error {
func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error { func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {
filepath := path.Join(srcBase, srcFile) filepath := path.Join(srcBase, srcFile)
stat, err := os.Stat(filepath) stat, err := os.Lstat(filepath)
if err != nil { if err != nil {
return err return err
} }
...@@ -247,28 +258,55 @@ func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) e ...@@ -247,28 +258,55 @@ func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) e
if err != nil { if err != nil {
return err 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 { for _, f := range files {
if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil { if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
return err return err
} }
} }
return nil return nil
} } else if stat.Mode()&os.ModeSymlink != 0 {
hdr, err := tar.FileInfoHeader(stat, filepath) //case soft link
if err != nil { hdr, _ := tar.FileInfoHeader(stat, filepath)
return err target, err := os.Readlink(filepath)
} if err != nil {
hdr.Name = destFile return err
if err := tw.WriteHeader(hdr); err != nil { }
return err
} hdr.Linkname = target
f, err := os.Open(filepath) hdr.Name = destFile
if err != nil { 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 return err
} }
defer f.Close() return nil
_, err = io.Copy(tw, f)
return err
} }
func untarAll(reader io.Reader, destFile, prefix string) error { func untarAll(reader io.Reader, destFile, prefix string) error {
...@@ -285,6 +323,7 @@ func untarAll(reader io.Reader, destFile, prefix string) error { ...@@ -285,6 +323,7 @@ func untarAll(reader io.Reader, destFile, prefix string) error {
break break
} }
entrySeq++ entrySeq++
mode := header.FileInfo().Mode()
outFileName := path.Join(destFile, header.Name[len(prefix):]) outFileName := path.Join(destFile, header.Name[len(prefix):])
baseName := path.Dir(outFileName) baseName := path.Dir(outFileName)
if err := os.MkdirAll(baseName, 0755); err != nil { if err := os.MkdirAll(baseName, 0755); err != nil {
...@@ -308,15 +347,27 @@ func untarAll(reader io.Reader, destFile, prefix string) error { ...@@ -308,15 +347,27 @@ func untarAll(reader io.Reader, destFile, prefix string) error {
outFileName = filepath.Join(outFileName, path.Base(header.Name)) outFileName = filepath.Join(outFileName, path.Base(header.Name))
} }
} }
outFile, err := os.Create(outFileName)
if err != nil { if mode&os.ModeSymlink != 0 {
return err err := os.Symlink(header.Linkname, outFileName)
} if err != nil {
defer outFile.Close() return err
if _, err := io.Copy(outFile, tarReader); 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 return nil
} }
......
...@@ -29,6 +29,13 @@ import ( ...@@ -29,6 +29,13 @@ import (
"testing" "testing"
) )
type FileType int
const (
RegularFile FileType = 0
SymLink FileType = 1
)
func TestExtractFileSpec(t *testing.T) { func TestExtractFileSpec(t *testing.T) {
tests := []struct { tests := []struct {
spec string spec string
...@@ -119,24 +126,34 @@ func TestTarUntar(t *testing.T) { ...@@ -119,24 +126,34 @@ func TestTarUntar(t *testing.T) {
}() }()
files := []struct { files := []struct {
name string name string
data string data string
fileType FileType
}{ }{
{ {
name: "foo", name: "foo",
data: "foobarbaz", data: "foobarbaz",
fileType: RegularFile,
}, },
{ {
name: "dir/blah", name: "dir/blah",
data: "bazblahfoo", data: "bazblahfoo",
fileType: RegularFile,
}, },
{ {
name: "some/other/directory/", name: "some/other/directory/",
data: "with more data here", data: "with more data here",
fileType: RegularFile,
}, },
{ {
name: "blah", name: "blah",
data: "same file name different data", data: "same file name different data",
fileType: RegularFile,
},
{
name: "gakki",
data: "/tmp/gakki",
fileType: SymLink,
}, },
} }
...@@ -146,20 +163,32 @@ func TestTarUntar(t *testing.T) { ...@@ -146,20 +163,32 @@ func TestTarUntar(t *testing.T) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
t.FailNow() t.FailNow()
} }
f, err := os.Create(filepath) if file.fileType == RegularFile {
if err != nil { f, err := os.Create(filepath)
t.Errorf("unexpected error: %v", err) if err != nil {
t.FailNow() t.Errorf("unexpected error: %v", err)
} t.FailNow()
defer f.Close() }
if _, err := io.Copy(f, bytes.NewBuffer([]byte(file.data))); err != nil { defer f.Close()
t.Errorf("unexpected error: %v", err) 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() t.FailNow()
} }
} }
writer := &bytes.Buffer{} 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) t.Errorf("unexpected error: %v", err)
} }
...@@ -170,16 +199,35 @@ func TestTarUntar(t *testing.T) { ...@@ -170,16 +199,35 @@ func TestTarUntar(t *testing.T) {
} }
for _, file := range files { for _, file := range files {
filepath := path.Join(dir, file.name) absPath := dir2 + strings.TrimPrefix(dir, os.TempDir())
f, err := os.Open(filepath) filepath := path.Join(absPath, file.name)
if err != nil {
t.Errorf("unexpected error: %v", err) if file.fileType == RegularFile {
} f, err := os.Open(filepath)
defer f.Close() if err != nil {
buff := &bytes.Buffer{} t.Errorf("unexpected error: %v", err)
io.Copy(buff, f) }
if file.data != string(buff.Bytes()) {
t.Errorf("expected: %s, saw: %s", file.data, string(buff.Bytes())) 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()
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment