Merge pull request #69573 from bjhaid/master

Opt out of chowning and chmoding from kubectl cp.
pull/58/head
k8s-ci-robot 2018-10-18 14:43:49 -07:00 committed by GitHub
commit 00dd32b167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 20 deletions

View File

@ -23,6 +23,7 @@ go_test(
srcs = ["cp_test.go"], srcs = ["cp_test.go"],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//pkg/kubectl/cmd/exec:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/scheme:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",

View File

@ -67,8 +67,9 @@ var (
) )
type CopyOptions struct { type CopyOptions struct {
Container string Container string
Namespace string Namespace string
NoPreserve bool
ClientConfig *restclient.Config ClientConfig *restclient.Config
Clientset kubernetes.Interface Clientset kubernetes.Interface
@ -98,6 +99,7 @@ func NewCmdCp(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.C
}, },
} }
cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Container name. If omitted, the first container in the pod will be chosen") cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&o.NoPreserve, "no-preserve", "", false, "The copied file/directory's ownership and permissions will not be preserved in the container")
return cmd return cmd
} }
@ -179,7 +181,7 @@ func (o *CopyOptions) Run(args []string) error {
if len(srcSpec.PodName) != 0 && len(destSpec.PodName) != 0 { if len(srcSpec.PodName) != 0 && len(destSpec.PodName) != 0 {
if _, err := os.Stat(args[0]); err == nil { if _, err := os.Stat(args[0]); err == nil {
return o.copyToPod(fileSpec{File: args[0]}, destSpec) return o.copyToPod(fileSpec{File: args[0]}, destSpec, &exec.ExecOptions{})
} }
return fmt.Errorf("src doesn't exist in local filesystem") return fmt.Errorf("src doesn't exist in local filesystem")
} }
@ -188,7 +190,7 @@ func (o *CopyOptions) Run(args []string) error {
return o.copyFromPod(srcSpec, destSpec) return o.copyFromPod(srcSpec, destSpec)
} }
if len(destSpec.PodName) != 0 { if len(destSpec.PodName) != 0 {
return o.copyToPod(srcSpec, destSpec) return o.copyToPod(srcSpec, destSpec, &exec.ExecOptions{})
} }
return fmt.Errorf("one of src or dest must be a remote file specification") return fmt.Errorf("one of src or dest must be a remote file specification")
} }
@ -216,7 +218,7 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
return o.execute(options) return o.execute(options)
} }
func (o *CopyOptions) copyToPod(src, dest fileSpec) error { func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) error {
if len(src.File) == 0 || len(dest.File) == 0 { if len(src.File) == 0 || len(dest.File) == 0 {
return errFileCannotBeEmpty return errFileCannotBeEmpty
} }
@ -238,30 +240,33 @@ func (o *CopyOptions) copyToPod(src, dest fileSpec) error {
err := makeTar(src.File, dest.File, writer) err := makeTar(src.File, dest.File, writer)
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}() }()
var cmdArr []string
// TODO: Improve error messages by first testing if 'tar' is present in the container? // TODO: Improve error messages by first testing if 'tar' is present in the container?
cmdArr := []string{"tar", "xf", "-"} if o.NoPreserve {
cmdArr = []string{"tar", "--no-same-permissions", "--no-same-owner", "-xf", "-"}
} else {
cmdArr = []string{"tar", "-xf", "-"}
}
destDir := path.Dir(dest.File) destDir := path.Dir(dest.File)
if len(destDir) > 0 { if len(destDir) > 0 {
cmdArr = append(cmdArr, "-C", destDir) cmdArr = append(cmdArr, "-C", destDir)
} }
options := &exec.ExecOptions{ options.StreamOptions = exec.StreamOptions{
StreamOptions: exec.StreamOptions{ IOStreams: genericclioptions.IOStreams{
IOStreams: genericclioptions.IOStreams{ In: reader,
In: reader, Out: o.Out,
Out: o.Out, ErrOut: o.ErrOut,
ErrOut: o.ErrOut,
},
Stdin: true,
Namespace: dest.PodNamespace,
PodName: dest.PodName,
}, },
Stdin: true,
Command: cmdArr, Namespace: dest.PodNamespace,
Executor: &exec.DefaultRemoteExecutor{}, PodName: dest.PodName,
} }
options.Command = cmdArr
options.Executor = &exec.DefaultRemoteExecutor{}
return o.execute(options) return o.execute(options)
} }

View File

@ -26,6 +26,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -35,6 +36,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
kexec "k8s.io/kubernetes/pkg/kubectl/cmd/exec"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
) )
@ -629,7 +631,7 @@ func TestCopyToPod(t *testing.T) {
} }
opts.Complete(tf, cmd) opts.Complete(tf, cmd)
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
err = opts.copyToPod(src, dest) err = opts.copyToPod(src, dest, &kexec.ExecOptions{})
//If error is NotFound error , it indicates that the //If error is NotFound error , it indicates that the
//request has been sent correctly. //request has been sent correctly.
//Treat this as no error. //Treat this as no error.
@ -643,6 +645,68 @@ func TestCopyToPod(t *testing.T) {
} }
} }
func TestCopyToPodNoPreserve(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
ns := scheme.Codecs
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
responsePod := &v1.Pod{}
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, responsePod))))}, nil
}),
}
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCp(tf, ioStreams)
srcFile, err := ioutil.TempDir("", "test")
if err != nil {
t.Errorf("unexpected error: %v", err)
t.FailNow()
}
defer os.RemoveAll(srcFile)
tests := map[string]struct {
expectedCmd []string
nopreserve bool
}{
"copy to pod no preserve user and permissions": {
expectedCmd: []string{"tar", "--no-same-permissions", "--no-same-owner", "-xf", "-", "-C", "."},
nopreserve: true,
},
"copy to pod preserve user and permissions": {
expectedCmd: []string{"tar", "-xf", "-", "-C", "."},
nopreserve: false,
},
}
opts := NewCopyOptions(ioStreams)
src := fileSpec{
File: srcFile,
}
dest := fileSpec{
PodNamespace: "pod-ns",
PodName: "pod-name",
File: "foo",
}
opts.Complete(tf, cmd)
for name, test := range tests {
t.Run(name, func(t *testing.T) {
options := &kexec.ExecOptions{}
opts.NoPreserve = test.nopreserve
err = opts.copyToPod(src, dest, options)
if !(reflect.DeepEqual(test.expectedCmd, options.Command)) {
t.Errorf("expected cmd: %v, got: %v", test.expectedCmd, options.Command)
}
})
}
}
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string