mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00
Introduce podman machine cp
command
Add a new `podman machine cp` subcommand to allow users to copy files or directories between a running Podman Machine and their host. Tests cover the following cases: - Copy a file from the host machine to the VM - Copy a directory from the host machine to the VM - Copy a file from the VM to the host machine - Copy a directory from the VM to the host machine - Copy a file to a directory - Copy a directory to a file Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
This commit is contained in:
158
cmd/podman/machine/cp.go
Normal file
158
cmd/podman/machine/cp.go
Normal file
@ -0,0 +1,158 @@
|
||||
//go:build amd64 || arm64
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/podman/v5/cmd/podman/registry"
|
||||
"github.com/containers/podman/v5/libpod/events"
|
||||
"github.com/containers/podman/v5/pkg/copy"
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
"github.com/containers/podman/v5/pkg/machine/define"
|
||||
"github.com/containers/podman/v5/pkg/machine/env"
|
||||
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
|
||||
"github.com/containers/podman/v5/pkg/specgen"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type cpOptions struct {
|
||||
Quiet bool
|
||||
Machine *vmconfigs.MachineConfig
|
||||
IsSrc bool
|
||||
SrcPath string
|
||||
DestPath string
|
||||
}
|
||||
|
||||
var (
|
||||
cpCmd = &cobra.Command{
|
||||
Use: "cp [options] SRC_PATH DEST_PATH",
|
||||
Short: "Securely copy contents between the virtual machine",
|
||||
Long: "Securely copy files or directories between the virtual machine and your host",
|
||||
PersistentPreRunE: machinePreRunE,
|
||||
RunE: cp,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: `podman machine cp ~/ca.crt podman-machine-default:/etc/containers/certs.d/ca.crt`,
|
||||
ValidArgsFunction: autocompleteMachineCp,
|
||||
}
|
||||
|
||||
cpOpts = cpOptions{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Command: cpCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
|
||||
flags := cpCmd.Flags()
|
||||
quietFlagName := "quiet"
|
||||
flags.BoolVarP(&cpOpts.Quiet, quietFlagName, "q", false, "Suppress copy status output")
|
||||
}
|
||||
|
||||
func cp(_ *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
srcMachine, srcPath, destMachine, destPath, err := copy.ParseSourceAndDestination(args[0], args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: This will most likely break hyperv or wsl machines with single-letter
|
||||
// names. It is most likely similar to https://github.com/containers/podman/issues/25218
|
||||
//
|
||||
// Passing an absolute windows path of the format <volume>:\<path> will cause
|
||||
// `copy.ParseSourceAndDestination` to think the volume is a Machine. Check
|
||||
// if the raw cmdline argument is a Windows host path.
|
||||
if specgen.IsHostWinPath(args[0]) {
|
||||
srcMachine = ""
|
||||
srcPath = args[0]
|
||||
}
|
||||
|
||||
if specgen.IsHostWinPath(args[1]) {
|
||||
destMachine = ""
|
||||
destPath = args[1]
|
||||
}
|
||||
|
||||
mc, err := resolveMachine(srcMachine, destMachine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := provider.State(mc, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state != define.Running {
|
||||
return fmt.Errorf("vm %q is not running", mc.Name)
|
||||
}
|
||||
|
||||
cpOpts.Machine = mc
|
||||
cpOpts.SrcPath = srcPath
|
||||
cpOpts.DestPath = destPath
|
||||
|
||||
err = secureCopy(&cpOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy failed: %s", err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Copy successful")
|
||||
newMachineEvent(events.Copy, events.Event{Name: mc.Name})
|
||||
return nil
|
||||
}
|
||||
|
||||
func secureCopy(opts *cpOptions) error {
|
||||
srcPath := opts.SrcPath
|
||||
destPath := opts.DestPath
|
||||
sshConfig := opts.Machine.SSH
|
||||
|
||||
username := sshConfig.RemoteUsername
|
||||
if cpOpts.Machine.HostUser.Rootful {
|
||||
username = "root"
|
||||
}
|
||||
username += "@localhost:"
|
||||
|
||||
if opts.IsSrc {
|
||||
srcPath = username + srcPath
|
||||
} else {
|
||||
destPath = username + destPath
|
||||
}
|
||||
|
||||
args := []string{"-r", "-i", sshConfig.IdentityPath, "-P", strconv.Itoa(sshConfig.Port)}
|
||||
args = append(args, machine.CommonSSHArgs()...)
|
||||
args = append(args, []string{srcPath, destPath}...)
|
||||
|
||||
cmd := exec.Command("scp", args...)
|
||||
if !opts.Quiet {
|
||||
cmd.Stdout = os.Stdout
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func resolveMachine(srcMachine, destMachine string) (*vmconfigs.MachineConfig, error) {
|
||||
if len(srcMachine) > 0 && len(destMachine) > 0 {
|
||||
return nil, errors.New("copying between two machines is unsupported")
|
||||
}
|
||||
|
||||
if len(srcMachine) == 0 && len(destMachine) == 0 {
|
||||
return nil, errors.New("a machine name must prefix either the source path or destination path")
|
||||
}
|
||||
|
||||
dirs, err := env.GetMachineDirs(provider.VMType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := destMachine
|
||||
if len(srcMachine) > 0 {
|
||||
cpOpts.IsSrc = true
|
||||
name = srcMachine
|
||||
}
|
||||
|
||||
return vmconfigs.LoadMachineByName(name, dirs)
|
||||
}
|
@ -68,6 +68,33 @@ func autocompleteMachineSSH(cmd *cobra.Command, args []string, toComplete string
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
// autocompleteMachineCp - Autocomplete machine cp command.
|
||||
func autocompleteMachineCp(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) < 2 {
|
||||
if i := strings.IndexByte(toComplete, ':'); i > -1 {
|
||||
// TODO: offer virtual machine path completion
|
||||
|
||||
// the user already set the machine name, so don't use the host file autocompletion
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// suggest machine when they match the input otherwise normal shell completion is used
|
||||
machines, _ := getMachines(toComplete)
|
||||
for _, machine := range machines {
|
||||
if strings.HasPrefix(machine, toComplete) {
|
||||
for i := range machines {
|
||||
machines[i] += ":"
|
||||
}
|
||||
return machines, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
}
|
||||
|
||||
return nil, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
// don't complete more than 2 args
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// autocompleteMachine - Autocomplete machines.
|
||||
func autocompleteMachine(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
|
63
docs/source/markdown/podman-machine-cp.1.md
Normal file
63
docs/source/markdown/podman-machine-cp.1.md
Normal file
@ -0,0 +1,63 @@
|
||||
% podman-machine-cp 1
|
||||
|
||||
## NAME
|
||||
podman\-machine\-cp - Securely copy contents between the host and the virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine cp** [*options*] *src_path* *dest_path*
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Use secure copy (scp) to copy files or directories between the virtual machine
|
||||
and your host machine.
|
||||
|
||||
`podman machine cp` does not support copying between two virtual machines,
|
||||
which would require two machines running simultaneously.
|
||||
|
||||
Additionally, `podman machine cp` will automatically do a recursive copy of
|
||||
files and directories.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
#### **--quiet**, **-q**
|
||||
|
||||
Suppress copy status output.
|
||||
|
||||
## EXAMPLES
|
||||
Copy a file from your host to the running Podman Machine.
|
||||
```
|
||||
$ podman machine cp ~/configuration.txt podman-machine-default:~/configuration.txt
|
||||
...
|
||||
Copy Successful
|
||||
```
|
||||
|
||||
Copy a file from the running Podman Machine to your host.
|
||||
```
|
||||
$ podman machine cp podman-machine-default:~/logs/log.txt ~/logs/podman-machine-default.txt
|
||||
...
|
||||
Copy Successful
|
||||
```
|
||||
|
||||
Copy a directory from your host to the running Podman Machine.
|
||||
```
|
||||
$ podman machine cp ~/.config podman-machine-default:~/.config
|
||||
...
|
||||
Copy Successful
|
||||
```
|
||||
|
||||
Copy a directory from the running Podman Machine to your host.
|
||||
```
|
||||
$ podman machine cp podman-machine-default:~/.config ~/podman-machine-default.config
|
||||
...
|
||||
Copy Successful
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**
|
||||
|
||||
## HISTORY
|
||||
February 2025, Originally compiled by Jake Correnti <jcorrent@redhat.com>
|
@ -24,22 +24,23 @@ Podman machine behaviour can be modified via the [machine] section in the contai
|
||||
|
||||
## SUBCOMMANDS
|
||||
|
||||
| Command | Man Page | Description |
|
||||
|---------|----------------------------------------------------------|---------------------------------------|
|
||||
| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info |
|
||||
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
|
||||
| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines |
|
||||
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
|
||||
| os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS |
|
||||
| reset | [podman-machine-reset(1)](podman-machine-reset.1.md) | Reset Podman machines and environment |
|
||||
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
|
||||
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Set a virtual machine setting |
|
||||
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
|
||||
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
|
||||
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
|
||||
| Command | Man Page | Description |
|
||||
|---------|----------------------------------------------------------|-----------------------------------------------------------------|
|
||||
| cp | [podman-machine-cp(1)](podman-machine-cp.1.md) | Securely copy contents between the host and the virtual machine |
|
||||
| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info |
|
||||
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
|
||||
| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines |
|
||||
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
|
||||
| os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS |
|
||||
| reset | [podman-machine-reset(1)](podman-machine-reset.1.md) | Reset Podman machines and environment |
|
||||
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
|
||||
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Set a virtual machine setting |
|
||||
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
|
||||
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
|
||||
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
|
||||
|
||||
## SEE ALSO
|
||||
**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**, **[podman-machine-reset(1)](podman-machine-reset.1.md)**, **containers.conf(5)**
|
||||
**[podman(1)](podman.1.md)**, **[podman-machine-cp(1)](podman-machine-cp.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**, **[podman-machine-reset(1)](podman-machine-reset.1.md)**, **containers.conf(5)**
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
37
pkg/machine/e2e/config_cp_test.go
Normal file
37
pkg/machine/e2e/config_cp_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package e2e_test
|
||||
|
||||
type cpMachine struct {
|
||||
quiet bool
|
||||
src string
|
||||
dest string
|
||||
|
||||
cmd []string
|
||||
}
|
||||
|
||||
func (c *cpMachine) buildCmd(m *machineTestBuilder) []string {
|
||||
cmd := []string{"machine", "cp"}
|
||||
|
||||
if c.quiet {
|
||||
cmd = append(cmd, "--quiet")
|
||||
}
|
||||
|
||||
cmd = append(cmd, c.src, c.dest)
|
||||
|
||||
c.cmd = cmd
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cpMachine) withQuiet() *cpMachine {
|
||||
c.quiet = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *cpMachine) withSrc(src string) *cpMachine {
|
||||
c.src = src
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *cpMachine) withDest(dest string) *cpMachine {
|
||||
c.dest = dest
|
||||
return c
|
||||
}
|
172
pkg/machine/e2e/cp_test.go
Normal file
172
pkg/machine/e2e/cp_test.go
Normal file
@ -0,0 +1,172 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("podman machine cp", func() {
|
||||
It("all tests", func() {
|
||||
// HOST FILE SYSTEM
|
||||
// ~/<ginkgo_tmp>
|
||||
// * foo.txt
|
||||
// - foo-dir
|
||||
// * bar.txt
|
||||
//
|
||||
// * guest-foo.txt
|
||||
// - guest-foo-dir
|
||||
// * bar.txt
|
||||
|
||||
// GUEST FILE SYSTEM
|
||||
// ~/
|
||||
// * foo.txt
|
||||
// - foo-dir
|
||||
// * bar.txt
|
||||
|
||||
var (
|
||||
file = "foo.txt"
|
||||
filePath = filepath.Join(GinkgoT().TempDir(), file)
|
||||
|
||||
directory = "foo-dir"
|
||||
directoryPath = filepath.Join(GinkgoT().TempDir(), directory)
|
||||
|
||||
fileInDirectory = "bar.txt"
|
||||
fileInDirectoryPath = filepath.Join(directoryPath, fileInDirectory)
|
||||
|
||||
guestToHostFile = "guest-foo.txt"
|
||||
guestToHostDir = "guest-foo-dir"
|
||||
)
|
||||
|
||||
_, err := os.Create(filePath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = os.MkdirAll(directoryPath, 0755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = os.Create(fileInDirectoryPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
name := randomString()
|
||||
initMachine := initMachine{}
|
||||
cp := cpMachine{}
|
||||
sshMachine := sshMachine{}
|
||||
|
||||
By("host file to guest")
|
||||
session, err := mb.setName(name).setCmd(initMachine.withImage(mb.imagePath).withNow()).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
|
||||
// copy the file into the guest
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(filePath).withDest(name + ":~/" + file)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
|
||||
// verify the guest has the file
|
||||
session, err = mb.setName(name).setCmd(sshMachine.withSSHCommand([]string{"ls"})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
Expect(session.outputToString()).To(Equal(file))
|
||||
|
||||
// try to copy the file to a location in the guest where permission will get denied
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(filePath).withDest(name + ":/etc/tmp.txt")).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
Expect(session.errorToString()).To(ContainSubstring("scp: dest open \"/etc/tmp.txt\": Permission denied"))
|
||||
|
||||
By("host directory to guest")
|
||||
// copy contents into the guest
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(directoryPath).withDest(name + ":~/" + directory)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
|
||||
// verify the content is in the guest
|
||||
session, err = mb.setName(name).setCmd(sshMachine.withSSHCommand([]string{"ls", directory})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
Expect(session.outputToString()).To(Equal(fileInDirectory))
|
||||
|
||||
By("guest file to host")
|
||||
// copy contents to the host
|
||||
guestToHostFilePath := filepath.Join(GinkgoT().TempDir(), guestToHostFile)
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(name + ":~/" + file).withDest(guestToHostFilePath)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
// check the contents are on the host
|
||||
Expect(guestToHostFilePath).To(BeARegularFile())
|
||||
|
||||
// this test can only be run on Unix systems. Windows does not
|
||||
// strictly enforce read-only permissions specified by `os.MkdirAll()`,
|
||||
// so we cannot easily make a read-only directory.
|
||||
if runtime.GOOS != "windows" {
|
||||
// try to copy the file to a location on the host where permission will get denied
|
||||
hostDirPath := filepath.Join(GinkgoT().TempDir(), "test-guest-copy-dir")
|
||||
err = os.MkdirAll(hostDirPath, 0444)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
hostFileInDirPath := filepath.Join(hostDirPath, file)
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(name + ":~/" + file).withDest(hostFileInDirPath)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: open local \"%s\": Permission denied", hostFileInDirPath)))
|
||||
}
|
||||
|
||||
By("guest directory to host")
|
||||
// copy contents to the host
|
||||
guestToHostDirPath := filepath.Join(GinkgoT().TempDir(), guestToHostDir)
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(name + ":~/" + directory).withDest(guestToHostDirPath)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
// check the contents are on the host
|
||||
Expect(filepath.Join(guestToHostDirPath, fileInDirectory)).To(BeARegularFile())
|
||||
|
||||
By("attempt copying file to a new directory")
|
||||
// copy the file to a guest directory
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(filePath).withDest(name + ":~/directory/")).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
Expect(session.errorToString()).To(ContainSubstring("scp: dest open \"directory/\": Failure"))
|
||||
|
||||
// try copying a guest file to a host directory
|
||||
hostDirPath := filepath.Join(GinkgoT().TempDir(), "directory") + string(filepath.Separator)
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(name + ":~/" + file).withDest(hostDirPath)).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
hostDirPath = filepath.ToSlash(hostDirPath)
|
||||
fallthrough
|
||||
case "darwin":
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: open local \"%s\": No such file or directory", hostDirPath)))
|
||||
case "linux":
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: open local \"%s\": Is a directory", hostDirPath)))
|
||||
}
|
||||
|
||||
By("attempt copying directory to a file")
|
||||
// try copying a local directory to a guest file
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(GinkgoT().TempDir() + string(filepath.Separator)).withDest(name + ":~/" + directory + "/" + fileInDirectory + "/")).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
Expect(session.errorToString()).To(ContainSubstring("/foo-dir/bar.txt\" exists but is not a directory"))
|
||||
|
||||
// try copying the guest directory to a local file
|
||||
session, err = mb.setCmd(cp.withQuiet().withSrc(name + ":~/" + directory + "/").withDest(filePath + string(filepath.Separator))).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(125))
|
||||
|
||||
hostLocalFilePath := filePath + string(filepath.Separator)
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
hostLocalFilePath = filepath.ToSlash(hostLocalFilePath)
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: open local \"%s\": No such file or directory", hostLocalFilePath+fileInDirectory)))
|
||||
case "darwin":
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: mkdir %s: Not a directory", hostLocalFilePath)))
|
||||
case "linux":
|
||||
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("scp: open local \"%s\": Not a directory", hostLocalFilePath+fileInDirectory)))
|
||||
}
|
||||
})
|
||||
})
|
@ -114,12 +114,7 @@ func commonNativeSSH(username, identityPath, name string, sshPort int, inputArgs
|
||||
port := strconv.Itoa(sshPort)
|
||||
interactive := true
|
||||
|
||||
args := []string{"-i", identityPath, "-p", port, sshDestination,
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=" + os.DevNull,
|
||||
"-o", "CheckHostIP=no",
|
||||
"-o", "LogLevel=ERROR", "-o", "SetEnv=LC_ALL="}
|
||||
args := append([]string{"-i", identityPath, "-p", port, sshDestination}, CommonSSHArgs()...)
|
||||
if len(inputArgs) > 0 {
|
||||
interactive = false
|
||||
args = append(args, inputArgs...)
|
||||
@ -138,3 +133,13 @@ func commonNativeSSH(username, identityPath, name string, sshPort int, inputArgs
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func CommonSSHArgs() []string {
|
||||
return []string{
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=" + os.DevNull,
|
||||
"-o", "CheckHostIP=no",
|
||||
"-o", "LogLevel=ERROR",
|
||||
"-o", "SetEnv=LC_ALL="}
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
|
||||
if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || IsHostWinPath(src) {
|
||||
// This is not a named volume
|
||||
overlayFlag := false
|
||||
chownFlag := false
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func isHostWinPath(path string) bool {
|
||||
func IsHostWinPath(path string) bool {
|
||||
return shouldResolveWinPaths() && strings.HasPrefix(path, `\\`) || hasWinDriveScheme(path, 0) || winPathExists(path)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user