Skip to content

Commit daa08cb

Browse files
feat: 增加从pod上传下载文件util
1 parent 49729de commit daa08cb

File tree

1 file changed

+309
-0
lines changed

1 file changed

+309
-0
lines changed

pkg/util/podfile/podfile.go

+309
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package podfile
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
coreV1 "k8s.io/api/core/v1"
10+
"k8s.io/client-go/kubernetes"
11+
"k8s.io/client-go/kubernetes/scheme"
12+
"k8s.io/client-go/rest"
13+
"k8s.io/client-go/tools/remotecommand"
14+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
15+
"os"
16+
"path"
17+
"path/filepath"
18+
"strings"
19+
)
20+
21+
type PodCp struct {
22+
K8sClient *kubernetes.Clientset
23+
RESTConfig *rest.Config
24+
Namespace string
25+
PodName string
26+
ContainerName string
27+
Command []string
28+
Stdin io.Reader
29+
Stdout io.Writer
30+
Stderr io.Writer
31+
Tty bool
32+
NoPreserve bool
33+
}
34+
35+
func NewPodConfig(namespace, podName, containerName string, restConfig *rest.Config, k8sClient *kubernetes.Clientset) *PodCp {
36+
return &PodCp{
37+
Namespace: namespace,
38+
PodName: podName,
39+
ContainerName: containerName,
40+
RESTConfig: restConfig,
41+
K8sClient: k8sClient,
42+
}
43+
}
44+
45+
func (p *PodCp) CopyToPod(srcPath, destPath string) error {
46+
reader, writer := io.Pipe()
47+
go func() {
48+
defer writer.Close()
49+
cmdutil.CheckErr(makeTar(srcPath, destPath, writer))
50+
}()
51+
p.Tty = false
52+
p.NoPreserve = false
53+
p.Stdin = reader
54+
p.Stdout = os.Stdout
55+
if p.NoPreserve {
56+
p.Command = []string{"tar", "--no-same-permissions", "--no-same-owner", "-xmf", "-"}
57+
} else {
58+
p.Command = []string{"tar", "-xmf", "-"}
59+
}
60+
var stderr bytes.Buffer
61+
p.Stderr = &stderr
62+
err := p.Exec(Upload)
63+
if err != nil {
64+
return fmt.Errorf(err.Error(), p.Stderr)
65+
}
66+
if len(stderr.Bytes()) != 0 {
67+
for _, line := range strings.Split(stderr.String(), "\n") {
68+
if len(strings.TrimSpace(line)) == 0 {
69+
continue
70+
}
71+
if !strings.Contains(strings.ToLower(line), "removing") {
72+
return fmt.Errorf(line)
73+
}
74+
}
75+
}
76+
return nil
77+
}
78+
79+
func (p *PodCp) CopyFromPod(filePath string, destPath string) error {
80+
reader, outStream := io.Pipe()
81+
82+
p.Command = []string{"tar", "cf", "-", filePath}
83+
p.Stdin = os.Stdin
84+
p.Stdout = outStream
85+
p.Stderr = os.Stderr
86+
87+
err := p.Exec(Download)
88+
if err != nil {
89+
return err
90+
}
91+
prefix := getPrefix(filePath)
92+
prefix = path.Clean(prefix)
93+
prefix = stripPathShortcuts(prefix)
94+
err = unTarAll(reader, destPath, prefix)
95+
return err
96+
}
97+
98+
type ActionType string
99+
100+
const Upload ActionType = "Upload"
101+
const Download ActionType = "Download"
102+
103+
func (p *PodCp) Exec(actionType ActionType) error {
104+
req := p.K8sClient.CoreV1().RESTClient().Get().
105+
Resource("pods").
106+
Name(p.PodName).
107+
Namespace(p.Namespace).
108+
SubResource("exec").
109+
VersionedParams(&coreV1.PodExecOptions{
110+
Command: p.Command,
111+
Container: p.ContainerName,
112+
Stdin: true,
113+
Stdout: true,
114+
Stderr: true,
115+
TTY: false,
116+
}, scheme.ParameterCodec)
117+
exec, err := remotecommand.NewSPDYExecutor(p.RESTConfig, "POST", req.URL())
118+
if err != nil {
119+
return err
120+
}
121+
122+
if actionType == Upload {
123+
err = p.stream(exec)
124+
}
125+
if actionType == Download {
126+
go func() {
127+
p.stream(exec)
128+
}()
129+
}
130+
return err
131+
}
132+
133+
func (p *PodCp) stream(exec remotecommand.Executor) error {
134+
return exec.Stream(remotecommand.StreamOptions{
135+
Stdin: p.Stdin,
136+
Stdout: p.Stdout,
137+
Stderr: p.Stderr,
138+
Tty: p.Tty,
139+
})
140+
}
141+
142+
func makeTar(srcPath, destPath string, writer io.Writer) error {
143+
// TODO: use compression here?
144+
tarWriter := tar.NewWriter(writer)
145+
defer tarWriter.Close()
146+
147+
srcPath = path.Clean(srcPath)
148+
destPath = path.Clean(destPath)
149+
return recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)
150+
}
151+
152+
func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {
153+
srcPath := path.Join(srcBase, srcFile)
154+
matchedPaths, err := filepath.Glob(srcPath)
155+
if err != nil {
156+
return err
157+
}
158+
for _, fpath := range matchedPaths {
159+
stat, err := os.Lstat(fpath)
160+
if err != nil {
161+
return err
162+
}
163+
if stat.IsDir() {
164+
files, err := ioutil.ReadDir(fpath)
165+
if err != nil {
166+
return err
167+
}
168+
if len(files) == 0 {
169+
//case empty directory
170+
hdr, _ := tar.FileInfoHeader(stat, fpath)
171+
hdr.Name = destFile
172+
if err := tw.WriteHeader(hdr); err != nil {
173+
return err
174+
}
175+
}
176+
for _, f := range files {
177+
if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
178+
return err
179+
}
180+
}
181+
return nil
182+
} else if stat.Mode()&os.ModeSymlink != 0 {
183+
//case soft link
184+
hdr, _ := tar.FileInfoHeader(stat, fpath)
185+
target, err := os.Readlink(fpath)
186+
if err != nil {
187+
return err
188+
}
189+
190+
hdr.Linkname = target
191+
hdr.Name = destFile
192+
if err := tw.WriteHeader(hdr); err != nil {
193+
return err
194+
}
195+
} else {
196+
//case regular file or other file type like pipe
197+
hdr, err := tar.FileInfoHeader(stat, fpath)
198+
if err != nil {
199+
return err
200+
}
201+
hdr.Name = destFile
202+
203+
if err := tw.WriteHeader(hdr); err != nil {
204+
return err
205+
}
206+
207+
f, err := os.Open(fpath)
208+
if err != nil {
209+
return err
210+
}
211+
defer f.Close()
212+
213+
if _, err := io.Copy(tw, f); err != nil {
214+
return err
215+
}
216+
return f.Close()
217+
}
218+
}
219+
return nil
220+
}
221+
222+
func unTarAll(reader io.Reader, destDir, prefix string) error {
223+
tarReader := tar.NewReader(reader)
224+
for {
225+
header, err := tarReader.Next()
226+
if err != nil {
227+
if err != io.EOF {
228+
return err
229+
}
230+
break
231+
}
232+
233+
if !strings.HasPrefix(header.Name, prefix) {
234+
return fmt.Errorf("tar contents corrupted")
235+
}
236+
237+
mode := header.FileInfo().Mode()
238+
destFileName := filepath.Join(destDir, header.Name[len(prefix):])
239+
240+
baseName := filepath.Dir(destFileName)
241+
if err := os.MkdirAll(baseName, 0755); err != nil {
242+
return err
243+
}
244+
if header.FileInfo().IsDir() {
245+
if err := os.MkdirAll(destFileName, 0755); err != nil {
246+
return err
247+
}
248+
continue
249+
}
250+
251+
evaledPath, err := filepath.EvalSymlinks(baseName)
252+
if err != nil {
253+
return err
254+
}
255+
256+
if mode&os.ModeSymlink != 0 {
257+
linkname := header.Linkname
258+
259+
if !filepath.IsAbs(linkname) {
260+
_ = filepath.Join(evaledPath, linkname)
261+
}
262+
263+
if err := os.Symlink(linkname, destFileName); err != nil {
264+
return err
265+
}
266+
} else {
267+
outFile, err := os.Create(destFileName)
268+
if err != nil {
269+
return err
270+
}
271+
defer outFile.Close()
272+
if _, err := io.Copy(outFile, tarReader); err != nil {
273+
return err
274+
}
275+
if err := outFile.Close(); err != nil {
276+
return err
277+
}
278+
}
279+
}
280+
281+
return nil
282+
}
283+
284+
func getPrefix(file string) string {
285+
return strings.TrimLeft(file, "/")
286+
}
287+
288+
// stripPathShortcuts removes any leading or trailing "../" from a given path
289+
func stripPathShortcuts(p string) string {
290+
291+
newPath := path.Clean(p)
292+
trimmed := strings.TrimPrefix(newPath, "../")
293+
294+
for trimmed != newPath {
295+
newPath = trimmed
296+
trimmed = strings.TrimPrefix(newPath, "../")
297+
}
298+
299+
// trim leftover {".", ".."}
300+
if newPath == "." || newPath == ".." {
301+
newPath = ""
302+
}
303+
304+
if len(newPath) > 0 && string(newPath[0]) == "/" {
305+
return newPath[1:]
306+
}
307+
308+
return newPath
309+
}

0 commit comments

Comments
 (0)