mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-19 02:37:24 +08:00
feat(climc,host): support container copy and copy-from (#21822)
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/compute"
|
||||
"yunion.io/x/onecloud/pkg/util/pod/stream/cp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -133,4 +135,53 @@ func init() {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
R(new(options.ContainerCopyOptions), "container-cp", "Container copy", func(s *mcclient.ClientSession, opts *options.ContainerCopyOptions) error {
|
||||
parts := strings.Split(opts.CONTAINER_ID_FILE, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid container id: %s", opts.CONTAINER_ID_FILE)
|
||||
}
|
||||
if opts.RawFile {
|
||||
fr, err := os.Open(opts.SRC_FILE)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open file: %v", opts.SRC_FILE)
|
||||
}
|
||||
defer fr.Close()
|
||||
if err := modules.Containers.CopyTo(s, parts[0], parts[1], fr); err != nil {
|
||||
return errors.Wrapf(err, "copy file to container")
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return cp.NewCopy().CopyToContainer(s, opts.SRC_FILE, cp.ContainerFileOpt{
|
||||
ContainerId: parts[0],
|
||||
File: parts[1],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
R(new(options.ContainerCopyOptions), "container-cp-from", "Container copy", func(s *mcclient.ClientSession, opts *options.ContainerCopyOptions) error {
|
||||
parts := strings.Split(opts.SRC_FILE, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid container id: %s", opts.CONTAINER_ID_FILE)
|
||||
}
|
||||
ctrId := parts[0]
|
||||
ctrFile := parts[1]
|
||||
destFile := opts.CONTAINER_ID_FILE
|
||||
if opts.RawFile {
|
||||
fw, err := os.Create(destFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "open file: %v", destFile)
|
||||
}
|
||||
defer fw.Close()
|
||||
if err := modules.Containers.CopyFrom(s, ctrId, ctrFile, fw); err != nil {
|
||||
return errors.Wrap(err, "copy from")
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return cp.NewCopy().CopyFromContainer(s, cp.ContainerFileOpt{
|
||||
ContainerId: ctrId,
|
||||
File: ctrFile,
|
||||
}, destFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,6 +192,9 @@ type ContainerExecInfoOutput struct {
|
||||
type ContainerExecInput struct {
|
||||
Command []string `json:"command"`
|
||||
Tty bool `json:"tty"`
|
||||
SetIO bool `json:"set_io"`
|
||||
Stdin bool `json:"stdin"`
|
||||
Stdout bool `json:"stdout"`
|
||||
}
|
||||
|
||||
type ContainerExecSyncInput struct {
|
||||
|
||||
@@ -2085,13 +2085,21 @@ func (s *sPodGuestInstance) ExecContainer(ctx context.Context, userCred mcclient
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get container cri id")
|
||||
}
|
||||
stderr := true
|
||||
if input.Tty {
|
||||
stderr = false
|
||||
}
|
||||
req := &runtimeapi.ExecRequest{
|
||||
ContainerId: criId,
|
||||
Cmd: input.Command,
|
||||
Tty: input.Tty,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
//Stderr: true,
|
||||
Stderr: stderr,
|
||||
}
|
||||
if input.SetIO {
|
||||
req.Stdin = input.Stdin
|
||||
req.Stdout = input.Stdout
|
||||
}
|
||||
resp, err := rCli.Exec(ctx, req)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,9 +21,11 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/httputils"
|
||||
|
||||
@@ -36,18 +38,32 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/pod/term"
|
||||
)
|
||||
|
||||
var (
|
||||
Containers ContainerManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Containers = ContainerManager{
|
||||
modules.NewComputeManager("container", "containers",
|
||||
[]string{"ID", "Name", "Guest_ID", "Status", "Started_At", "Last_Finished_At", "Restart_Count", "Spec"},
|
||||
[]string{}),
|
||||
}
|
||||
modules.RegisterCompute(&Containers)
|
||||
}
|
||||
|
||||
type ContainerManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
func (man ContainerManager) SetupTTY(in io.Reader, out io.Writer, errOut io.Writer, raw bool) term.TTY {
|
||||
/*t := term.TTY{
|
||||
t := term.TTY{
|
||||
Out: out,
|
||||
}
|
||||
if in == nil {
|
||||
t.In = nil
|
||||
t.Raw = false
|
||||
return t
|
||||
}*/
|
||||
}
|
||||
return term.TTY{
|
||||
In: in,
|
||||
Out: out,
|
||||
@@ -97,6 +113,54 @@ func (man ContainerManager) Exec(s *mcclient.ClientSession, id string, opt *api.
|
||||
return t.Safe(fn)
|
||||
}
|
||||
|
||||
type ContainerExecInput struct {
|
||||
Command []string
|
||||
Tty bool
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
func (man ContainerManager) ExecV2(s *mcclient.ClientSession, id string, opt *ContainerExecInput) error {
|
||||
info, err := man.GetSpecific(s, id, "exec-info", nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get exec info")
|
||||
}
|
||||
infoOut := new(api.ContainerExecInfoOutput)
|
||||
info.Unmarshal(infoOut)
|
||||
apiInput := &api.ContainerExecInput{
|
||||
Command: opt.Command,
|
||||
Tty: opt.Tty,
|
||||
SetIO: true,
|
||||
Stdin: opt.Stdin != nil,
|
||||
Stdout: opt.Stdout != nil,
|
||||
}
|
||||
urlLoc := fmt.Sprintf("%s/pods/%s/containers/%s/exec?%s", infoOut.HostUri, infoOut.PodId, infoOut.ContainerId, jsonutils.Marshal(apiInput).QueryString())
|
||||
url, err := url.Parse(urlLoc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parse url: %s", urlLoc)
|
||||
}
|
||||
exec, err := remotecommand.NewSPDYExecutor("POST", url)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "NewSPDYExecutor")
|
||||
}
|
||||
headers := mcclient.GetTokenHeaders(s.GetToken())
|
||||
|
||||
t := man.SetupTTY(opt.Stdin, opt.Stdout, opt.Stderr, true)
|
||||
sizeQueue := t.MonitorSize(t.GetSize())
|
||||
fn := func() error {
|
||||
return exec.Stream(remotecommand.StreamOptions{
|
||||
Stdin: opt.Stdin,
|
||||
Stdout: opt.Stdout,
|
||||
Stderr: opt.Stderr,
|
||||
Tty: opt.Tty,
|
||||
TerminalSizeQueue: sizeQueue,
|
||||
Header: headers,
|
||||
})
|
||||
}
|
||||
return t.Safe(fn)
|
||||
}
|
||||
|
||||
func (man ContainerManager) Log(s *mcclient.ClientSession, id string, opt *api.PodLogOptions) (io.ReadCloser, error) {
|
||||
info, err := man.GetSpecific(s, id, "exec-info", nil)
|
||||
if err != nil {
|
||||
@@ -142,15 +206,61 @@ func (man ContainerManager) LogToWriter(s *mcclient.ClientSession, id string, op
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
Containers ContainerManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Containers = ContainerManager{
|
||||
modules.NewComputeManager("container", "containers",
|
||||
[]string{"ID", "Name", "Guest_ID", "Status", "Started_At", "Last_Finished_At", "Restart_Count", "Spec"},
|
||||
[]string{}),
|
||||
func (man ContainerManager) EnsureDir(s *mcclient.ClientSession, ctrId string, dirName string) error {
|
||||
opt := &ContainerExecInput{
|
||||
Command: []string{"mkdir", "-p", dirName},
|
||||
Tty: false,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
modules.RegisterCompute(&Containers)
|
||||
return man.ExecV2(s, ctrId, opt)
|
||||
}
|
||||
|
||||
func (man ContainerManager) CopyTo(s *mcclient.ClientSession, ctrId string, destPath string, in io.Reader) error {
|
||||
destDir := path.Dir(destPath)
|
||||
if err := man.EnsureDir(s, ctrId, destDir); err != nil {
|
||||
return errors.Wrapf(err, "ensure dir %s", destDir)
|
||||
}
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
go func() {
|
||||
defer writer.Close()
|
||||
written, err := io.Copy(writer, in)
|
||||
if err != nil {
|
||||
log.Errorf("copy reader to writer, written %d, error: %v", written, err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctrCmd := []string{"sh", "-c", fmt.Sprintf("cat - > %s", destPath)}
|
||||
opt := &ContainerExecInput{
|
||||
Command: ctrCmd,
|
||||
Tty: false,
|
||||
Stdin: reader,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
return man.ExecV2(s, ctrId, opt)
|
||||
}
|
||||
|
||||
func (man ContainerManager) CopyFrom(s *mcclient.ClientSession, ctrId string, ctrFile string, out io.Writer) error {
|
||||
reader, outStream := io.Pipe()
|
||||
opts := &ContainerExecInput{
|
||||
Command: []string{"cat", ctrFile},
|
||||
Tty: false,
|
||||
Stdin: nil,
|
||||
Stdout: outStream,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
go func() {
|
||||
defer outStream.Close()
|
||||
if err := man.ExecV2(s, ctrId, opts); err != nil {
|
||||
log.Errorf("compute.Containers.ExecV2: %v", err)
|
||||
}
|
||||
}()
|
||||
written, err := io.Copy(out, reader)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "copy from reader written: %d", written)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -497,3 +497,9 @@ func (o *ContainerRemoveVolumeMountPostOverlayOptions) Params() (jsonutils.JSONO
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
type ContainerCopyOptions struct {
|
||||
SRC_FILE string
|
||||
CONTAINER_ID_FILE string
|
||||
RawFile bool
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cp
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
)
|
||||
|
||||
type ICopy interface {
|
||||
CopyToContainer(s *mcclient.ClientSession, srcPath string, dest ContainerFileOpt) error
|
||||
CopyFromContainer(s *mcclient.ClientSession, src ContainerFileOpt, destPath string) error
|
||||
}
|
||||
|
||||
type sCopy struct {
|
||||
noPreserve bool
|
||||
ExecParentCmdName string
|
||||
}
|
||||
|
||||
func NewCopy() ICopy {
|
||||
return &sCopy{}
|
||||
}
|
||||
|
||||
type ContainerFileOpt struct {
|
||||
ContainerId string
|
||||
File string
|
||||
}
|
||||
|
||||
var ErrFileCannotBeEmpty = errors.Error("filepath can not be empty")
|
||||
|
||||
func (o *sCopy) CopyFromContainer(s *mcclient.ClientSession, src ContainerFileOpt, destPath string) error {
|
||||
if len(src.File) == 0 || len(destPath) == 0 {
|
||||
return ErrFileCannotBeEmpty
|
||||
}
|
||||
|
||||
reader, outStream := io.Pipe()
|
||||
opts := &compute.ContainerExecInput{
|
||||
Command: []string{"tar", "cf", "-", src.File},
|
||||
Tty: false,
|
||||
Stdin: nil,
|
||||
Stdout: outStream,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer outStream.Close()
|
||||
if err := compute.Containers.ExecV2(s, src.ContainerId, opts); err != nil {
|
||||
log.Errorf("compute.Containers.ExecV2: %v", err)
|
||||
}
|
||||
}()
|
||||
prefix := getPrefix(src.File)
|
||||
prefix = path.Clean(prefix)
|
||||
// remove extraneous path shortcuts - these could occur if a path contained extra "../
|
||||
// and attempted to navigate beyond "/" in a remote filesystem
|
||||
prefix = stripPathShortcuts(prefix)
|
||||
return o.untarAll(src, reader, destPath, prefix)
|
||||
}
|
||||
|
||||
func getPrefix(file string) string {
|
||||
// tar strips the leading '/' if it's there, so we will too
|
||||
return strings.TrimLeft(file, "/")
|
||||
}
|
||||
|
||||
// stripPathShortcuts removes any leading or trailing "../" from a given path
|
||||
func stripPathShortcuts(p string) string {
|
||||
newPath := path.Clean(p)
|
||||
trimmed := strings.TrimPrefix(newPath, "../")
|
||||
|
||||
for trimmed != newPath {
|
||||
newPath = trimmed
|
||||
trimmed = strings.TrimPrefix(newPath, "../")
|
||||
}
|
||||
|
||||
// trim leftover {".", ".."}
|
||||
if newPath == "." || newPath == ".." {
|
||||
newPath = ""
|
||||
}
|
||||
|
||||
if len(newPath) > 0 && string(newPath[0]) == "/" {
|
||||
return newPath[1:]
|
||||
}
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
func (o *sCopy) untarAll(src ContainerFileOpt, reader io.Reader, destDir, prefix string) error {
|
||||
symlinkWarningPrinted := false
|
||||
// TODO: use compression here?
|
||||
tarReader := tar.NewReader(reader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// All the files will start with the prefix, which is the directory where
|
||||
// they were located on the pod, we need to strip down that prefix, but
|
||||
// if the prefix is missing it means the tar was tempered with.
|
||||
// For the case where prefix is empty we need to ensure that the path
|
||||
// is not absolute, which also indicates the tar file was tempered with.
|
||||
if !strings.HasPrefix(header.Name, prefix) {
|
||||
return fmt.Errorf("tar contents corrupted")
|
||||
}
|
||||
|
||||
// basic file information
|
||||
mode := header.FileInfo().Mode()
|
||||
destFileName := filepath.Join(destDir, header.Name[len(prefix):])
|
||||
|
||||
if !isDestRelative(destDir, destFileName) {
|
||||
fmt.Fprintf(os.Stderr, "warning: file %q is outside target destination, skipping\n", destFileName)
|
||||
continue
|
||||
}
|
||||
|
||||
baseName := filepath.Dir(destFileName)
|
||||
if err := os.MkdirAll(baseName, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if header.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(destFileName, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if mode&os.ModeSymlink != 0 {
|
||||
if !symlinkWarningPrinted && len(o.ExecParentCmdName) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: skipping symlink: %q -> %q\n", destFileName, header.Linkname)
|
||||
symlinkWarningPrinted = true
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "warning: skipping symlink: %q -> %q\n", destFileName, header.Linkname)
|
||||
continue
|
||||
}
|
||||
outFile, err := os.Create(destFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := outFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDestRelative returns true if dest is pointing outside the base directory,
|
||||
// false otherwise.
|
||||
func isDestRelative(base, dest string) bool {
|
||||
relative, err := filepath.Rel(base, dest)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return relative == "." || relative == stripPathShortcuts(relative)
|
||||
}
|
||||
|
||||
func (o *sCopy) CopyToContainer(s *mcclient.ClientSession, srcFile string, dest ContainerFileOpt) error {
|
||||
if len(srcFile) == 0 || len(dest.File) == 0 {
|
||||
return ErrFileCannotBeEmpty
|
||||
}
|
||||
if _, err := os.Stat(srcFile); err != nil {
|
||||
return errors.Wrapf(err, "check source file: %s", srcFile)
|
||||
}
|
||||
reader, writer := io.Pipe()
|
||||
// strip trailing slash (if any)
|
||||
if dest.File != "/" && strings.HasSuffix(string(dest.File[len(dest.File)-1]), "/") {
|
||||
dest.File = dest.File[:len(dest.File)-1]
|
||||
}
|
||||
if err := o.checkDestinationIsDir(s, dest); err == nil {
|
||||
// If no error, dest.File was found to be a directory.
|
||||
// Copy specified src info it
|
||||
dest.File = dest.File + "/" + path.Base(srcFile)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer writer.Close()
|
||||
if err := makeTar(srcFile, dest.File, writer); err != nil {
|
||||
log.Errorf("makeTar error: %v", err)
|
||||
}
|
||||
}()
|
||||
var cmdArr []string
|
||||
|
||||
if o.noPreserve {
|
||||
cmdArr = []string{"tar", "--no-same-permissions", "--no-same-owner", "-xmf", "-"}
|
||||
} else {
|
||||
cmdArr = []string{"tar", "-xmf", "-"}
|
||||
}
|
||||
destDir := path.Dir(dest.File)
|
||||
if len(destDir) > 0 {
|
||||
cmdArr = append(cmdArr, "-C", destDir)
|
||||
}
|
||||
|
||||
opt := &compute.ContainerExecInput{
|
||||
Command: cmdArr,
|
||||
Tty: false,
|
||||
Stdin: reader,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
return compute.Containers.ExecV2(s, dest.ContainerId, opt)
|
||||
}
|
||||
|
||||
func (o *sCopy) checkDestinationIsDir(s *mcclient.ClientSession, dest ContainerFileOpt) error {
|
||||
opt := &compute.ContainerExecInput{
|
||||
Command: []string{"test", "-d", dest.File},
|
||||
Tty: false,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
return compute.Containers.ExecV2(s, dest.ContainerId, opt)
|
||||
}
|
||||
|
||||
func makeTar(srcPath, destPath string, writer io.Writer) error {
|
||||
tarWriter := tar.NewWriter(writer)
|
||||
defer tarWriter.Close()
|
||||
|
||||
srcPath = path.Clean(srcPath)
|
||||
destPath = path.Clean(destPath)
|
||||
return recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)
|
||||
}
|
||||
|
||||
func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {
|
||||
srcPath := path.Join(srcBase, srcFile)
|
||||
matchedPaths, err := filepath.Glob(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fpath := range matchedPaths {
|
||||
stat, err := os.Lstat(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
files, err := ioutil.ReadDir(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
//case empty directory
|
||||
hdr, _ := tar.FileInfoHeader(stat, fpath)
|
||||
hdr.Name = destFile
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else if stat.Mode()&os.ModeSymlink != 0 {
|
||||
//case soft link
|
||||
hdr, _ := tar.FileInfoHeader(stat, fpath)
|
||||
target, err := os.Readlink(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hdr.Linkname = target
|
||||
hdr.Name = destFile
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
//case regular file or other file type like pipe
|
||||
hdr, err := tar.FileInfoHeader(stat, fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = destFile
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(tw, f); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cp // import "yunion.io/x/onecloud/pkg/util/pod/stream/cp"
|
||||
Reference in New Issue
Block a user