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

Skip to content

Commit a198a57

Browse files
committed
add kubectl cp
1 parent 37dc74f commit a198a57

7 files changed

Lines changed: 525 additions & 0 deletions

File tree

.generated_docs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ docs/man/man1/kubectl-config-view.1
3333
docs/man/man1/kubectl-config.1
3434
docs/man/man1/kubectl-convert.1
3535
docs/man/man1/kubectl-cordon.1
36+
docs/man/man1/kubectl-cp.1
3637
docs/man/man1/kubectl-create-configmap.1
3738
docs/man/man1/kubectl-create-deployment.1
3839
docs/man/man1/kubectl-create-namespace.1
@@ -107,6 +108,7 @@ docs/user-guide/kubectl/kubectl_config_use-context.md
107108
docs/user-guide/kubectl/kubectl_config_view.md
108109
docs/user-guide/kubectl/kubectl_convert.md
109110
docs/user-guide/kubectl/kubectl_cordon.md
111+
docs/user-guide/kubectl/kubectl_cp.md
110112
docs/user-guide/kubectl/kubectl_create.md
111113
docs/user-guide/kubectl/kubectl_create_configmap.md
112114
docs/user-guide/kubectl/kubectl_create_deployment.md
@@ -165,6 +167,7 @@ docs/yaml/kubectl/kubectl_completion.yaml
165167
docs/yaml/kubectl/kubectl_config.yaml
166168
docs/yaml/kubectl/kubectl_convert.yaml
167169
docs/yaml/kubectl/kubectl_cordon.yaml
170+
docs/yaml/kubectl/kubectl_cp.yaml
168171
docs/yaml/kubectl/kubectl_create.yaml
169172
docs/yaml/kubectl/kubectl_delete.yaml
170173
docs/yaml/kubectl/kubectl_describe.yaml

docs/man/man1/kubectl-cp.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This file is autogenerated, but we've stopped checking such files into the
2+
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
3+
populate this file.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
2+
3+
<!-- BEGIN STRIP_FOR_RELEASE -->
4+
5+
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
6+
width="25" height="25">
7+
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
8+
width="25" height="25">
9+
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
10+
width="25" height="25">
11+
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
12+
width="25" height="25">
13+
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
14+
width="25" height="25">
15+
16+
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
17+
18+
If you are using a released version of Kubernetes, you should
19+
refer to the docs that go with that version.
20+
21+
Documentation for other releases can be found at
22+
[releases.k8s.io](http://releases.k8s.io).
23+
</strong>
24+
--
25+
26+
<!-- END STRIP_FOR_RELEASE -->
27+
28+
<!-- END MUNGE: UNVERSIONED_WARNING -->
29+
30+
This file is autogenerated, but we've stopped checking such files into the
31+
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
32+
populate this file.
33+
34+
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
35+
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_cp.md?pixel)]()
36+
<!-- END MUNGE: GENERATED_ANALYTICS -->

docs/yaml/kubectl/kubectl_cp.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This file is autogenerated, but we've stopped checking such files into the
2+
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
3+
populate this file.

pkg/kubectl/cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
265265
NewCmdExec(f, in, out, err),
266266
NewCmdPortForward(f, out, err),
267267
NewCmdProxy(f, out),
268+
NewCmdCp(f, in, out, err),
268269
},
269270
},
270271
{

pkg/kubectl/cmd/cp.go

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

0 commit comments

Comments
 (0)