Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5266230

Browse files
committed
add kubectl cp
1 parent 5c9ac89 commit 5266230

3 files changed

Lines changed: 384 additions & 0 deletions

File tree

pkg/kubectl/cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
290290
NewCmdExec(f, in, out, err),
291291
NewCmdPortForward(f, out, err),
292292
NewCmdProxy(f, out),
293+
NewCmdCp(f, in, out, err),
293294
},
294295
},
295296
{

pkg/kubectl/cmd/cp.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
/*
2+
Copyright 2014 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"archive/tar"
21+
"fmt"
22+
"io"
23+
"io/ioutil"
24+
"os"
25+
"path"
26+
"strings"
27+
28+
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
29+
30+
"github.com/renstrom/dedent"
31+
"github.com/spf13/cobra"
32+
)
33+
34+
var (
35+
cp_example = dedent.Dedent(`
36+
# Copy /tmp/foo local file to /tmp/bar in a remote pod
37+
kubectl cp /tmp/foo <some-namespace>/<some-pod>:/tmp/bar
38+
39+
# Copy /tmp/foo from a remote pod to /tmp/bar locally
40+
kubectl cp <some-namespace>/<some-pod>:/tmp/foo /tmp/bar`)
41+
42+
cpUsageStr = dedent.Dedent(`
43+
expected 'cp <file-spec-src> <file-spec-dest>'.
44+
<file-spec> is:
45+
[namespace/]pod-name:/file/path for a remote file
46+
/file/path for a local file`)
47+
)
48+
49+
// NewCmdCp creates a new Copy command.
50+
func NewCmdCp(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
51+
cmd := &cobra.Command{
52+
Use: "cp <file-spec-src> <file-spec-dest>",
53+
Short: "Copy files to and from containers.",
54+
Long: "Copy files to and from containers.",
55+
Example: cp_example,
56+
Run: func(cmd *cobra.Command, args []string) {
57+
cmdutil.CheckErr(runCopy(f, cmd, cmdOut, cmdErr, args))
58+
},
59+
}
60+
cmd.Flags().StringP("container", "c", "", "Container name. If omitted, the first container in the pod will be chosen")
61+
62+
return cmd
63+
}
64+
65+
type fileSpec struct {
66+
PodNamespace string
67+
PodName string
68+
File string
69+
}
70+
71+
func extractFileSpec(arg string) (fileSpec, error) {
72+
pieces := strings.Split(arg, ":")
73+
if len(pieces) == 1 {
74+
return fileSpec{File: arg}, nil
75+
}
76+
if len(pieces) != 2 {
77+
return fileSpec{}, fmt.Errorf("Unexpected fileSpec: %s, expected [[namespace/]pod:]file/path", arg)
78+
}
79+
file := pieces[1]
80+
81+
pieces = strings.Split(pieces[0], "/")
82+
if len(pieces) == 1 {
83+
return fileSpec{
84+
PodName: pieces[0],
85+
File: file,
86+
}, nil
87+
}
88+
if len(pieces) == 2 {
89+
return fileSpec{
90+
PodNamespace: pieces[0],
91+
PodName: pieces[1],
92+
File: file,
93+
}, nil
94+
}
95+
96+
return fileSpec{}, fmt.Errorf("Unexpected file spec: %s, expected [[namespace/]pod:]file/path", arg)
97+
}
98+
99+
func runCopy(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, args []string) error {
100+
if len(args) != 2 {
101+
return cmdutil.UsageError(cmd, cpUsageStr)
102+
}
103+
srcSpec, err := extractFileSpec(args[0])
104+
if err != nil {
105+
return err
106+
}
107+
destSpec, err := extractFileSpec(args[1])
108+
if err != nil {
109+
return err
110+
}
111+
if len(srcSpec.PodName) != 0 {
112+
return copyFromPod(f, cmd, out, cmderr, srcSpec, destSpec)
113+
}
114+
if len(destSpec.PodName) != 0 {
115+
return copyToPod(f, cmd, out, cmderr, srcSpec, destSpec)
116+
}
117+
return cmdutil.UsageError(cmd, "One of src or dest must be a remote file specification")
118+
}
119+
120+
func makeTar(filepath string, writer io.Writer) error {
121+
tarWriter := tar.NewWriter(writer)
122+
defer tarWriter.Close()
123+
return recursiveTar(path.Dir(filepath), path.Base(filepath), tarWriter)
124+
}
125+
126+
func recursiveTar(base, file string, tw *tar.Writer) error {
127+
filepath := path.Join(base, file)
128+
fmt.Printf("Starting to archive: %s\n", filepath)
129+
stat, err := os.Stat(filepath)
130+
if err != nil {
131+
return err
132+
}
133+
if stat.IsDir() {
134+
files, err := ioutil.ReadDir(filepath)
135+
if err != nil {
136+
return err
137+
}
138+
for _, f := range files {
139+
if err := recursiveTar(base, path.Join(file, f.Name()), tw); err != nil {
140+
return err
141+
}
142+
}
143+
return nil
144+
}
145+
hdr, err := tar.FileInfoHeader(stat, filepath)
146+
if err != nil {
147+
return err
148+
}
149+
hdr.Name = file
150+
if err := tw.WriteHeader(hdr); err != nil {
151+
return err
152+
}
153+
f, err := os.Open(filepath)
154+
if err != nil {
155+
return err
156+
}
157+
defer f.Close()
158+
fmt.Printf("Copying %s to archive\n", filepath)
159+
_, err = io.Copy(tw, f)
160+
return err
161+
}
162+
163+
func copyToPod(f cmdutil.Factory, cmd *cobra.Command, stdout, stderr io.Writer, src, dest fileSpec) error {
164+
reader, writer := io.Pipe()
165+
go func() {
166+
defer writer.Close()
167+
err := makeTar(src.File, writer)
168+
cmdutil.CheckErr(err)
169+
}()
170+
171+
cmdArr := []string{"tar", "xf", "-"}
172+
destDir := path.Dir(dest.File)
173+
if len(destDir) > 0 {
174+
cmdArr = append(cmdArr, "-C", destDir)
175+
}
176+
177+
options := &ExecOptions{
178+
StreamOptions: StreamOptions{
179+
In: reader,
180+
Out: stdout,
181+
Err: stderr,
182+
Stdin: true,
183+
184+
Namespace: dest.PodNamespace,
185+
PodName: dest.PodName,
186+
},
187+
188+
Command: cmdArr,
189+
Executor: &DefaultRemoteExecutor{},
190+
}
191+
return execute(f, cmd, options)
192+
}
193+
194+
func copyFromPod(f cmdutil.Factory, cmd *cobra.Command, out, cmderr io.Writer, src, dest fileSpec) error {
195+
reader, outStream := io.Pipe()
196+
options := &ExecOptions{
197+
StreamOptions: StreamOptions{
198+
In: nil,
199+
Out: outStream,
200+
Err: cmderr,
201+
202+
Namespace: src.PodNamespace,
203+
PodName: src.PodName,
204+
},
205+
206+
Command: []string{"tar", "cf", "-", src.File},
207+
Executor: &DefaultRemoteExecutor{},
208+
}
209+
210+
go func() {
211+
defer outStream.Close()
212+
execute(f, cmd, options)
213+
}()
214+
prefix := getPrefix(src.File)
215+
216+
return untarAll(reader, dest.File, prefix)
217+
}
218+
219+
func untarAll(reader io.Reader, destFile, prefix string) error {
220+
tarReader := tar.NewReader(reader)
221+
for {
222+
header, err := tarReader.Next()
223+
if err != nil {
224+
if err != io.EOF {
225+
return err
226+
}
227+
break
228+
}
229+
outFileName := path.Join(destFile, header.Name[len(prefix):])
230+
baseName := path.Dir(outFileName)
231+
if err := os.MkdirAll(baseName, 0755); err != nil {
232+
return err
233+
}
234+
if header.FileInfo().IsDir() {
235+
os.MkdirAll(outFileName, 0755)
236+
continue
237+
}
238+
outFile, err := os.Create(outFileName)
239+
if err != nil {
240+
return err
241+
}
242+
defer outFile.Close()
243+
io.Copy(outFile, tarReader)
244+
}
245+
return nil
246+
}
247+
248+
func getPrefix(file string) string {
249+
if file[0] == '/' {
250+
// tar strips the leading '/' if it's there, so we will too
251+
return file[1:]
252+
}
253+
return file
254+
}
255+
256+
func execute(f cmdutil.Factory, cmd *cobra.Command, options *ExecOptions) error {
257+
if len(options.Namespace) == 0 {
258+
namespace, _, err := f.DefaultNamespace()
259+
if err != nil {
260+
return err
261+
}
262+
options.Namespace = namespace
263+
}
264+
265+
container := cmdutil.GetFlagString(cmd, "container")
266+
if len(container) > 0 {
267+
options.ContainerName = container
268+
}
269+
270+
config, err := f.ClientConfig()
271+
if err != nil {
272+
return err
273+
}
274+
options.Config = config
275+
276+
clientset, err := f.ClientSet()
277+
if err != nil {
278+
return err
279+
}
280+
options.PodClient = clientset.Core()
281+
282+
if err := options.Validate(); err != nil {
283+
return err
284+
}
285+
286+
if err := options.Run(); err != nil {
287+
return err
288+
}
289+
return nil
290+
}

pkg/kubectl/cmd/cp_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Copyright 2014 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestExtractFileSpec(t *testing.T) {
24+
tests := []struct {
25+
spec string
26+
expectedPod string
27+
expectedNamespace string
28+
expectedFile string
29+
expectErr bool
30+
}{
31+
{
32+
spec: "namespace/pod:/some/file",
33+
expectedPod: "pod",
34+
expectedNamespace: "namespace",
35+
expectedFile: "/some/file",
36+
},
37+
{
38+
spec: "pod:/some/file",
39+
expectedPod: "pod",
40+
expectedFile: "/some/file",
41+
},
42+
{
43+
spec: "/some/file",
44+
expectedFile: "/some/file",
45+
},
46+
{
47+
spec: "some:bad:spec",
48+
expectErr: true,
49+
},
50+
}
51+
for _, test := range tests {
52+
spec, err := extractFileSpec(test.spec)
53+
if test.expectErr && err == nil {
54+
t.Errorf("unexpected non-error")
55+
continue
56+
}
57+
if err != nil && !test.expectErr {
58+
t.Errorf("unexpected error: %v", err)
59+
continue
60+
}
61+
if spec.PodName != test.expectedPod {
62+
t.Errorf("expected: %s, saw: %s", test.expectedPod, spec.PodName)
63+
}
64+
if spec.PodNamespace != test.expectedNamespace {
65+
t.Errorf("expected: %s, saw: %s", test.expectedNamespace, spec.PodNamespace)
66+
}
67+
if spec.File != test.expectedFile {
68+
t.Errorf("expected: %s, saw: %s", test.expectedFile, spec.File)
69+
}
70+
}
71+
}
72+
73+
func TestGetPrefix(t *testing.T) {
74+
tests := []struct {
75+
input string
76+
expected string
77+
}{
78+
{
79+
input: "/foo/bar",
80+
expected: "foo/bar",
81+
},
82+
{
83+
input: "foo/bar",
84+
expected: "foo/bar",
85+
},
86+
}
87+
for _, test := range tests {
88+
out := getPrefix(test.input)
89+
if out != test.expected {
90+
t.Errorf("expected: %s, saw: %s", test.expected, out)
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)