mirror of https://github.com/docker/docs.git
				
				
				
			
		
			
				
	
	
		
			170 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| package commands
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/docker/machine/libmachine"
 | |
| 	"github.com/docker/machine/libmachine/log"
 | |
| 	"github.com/docker/machine/libmachine/persist"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	errWrongNumberArguments = errors.New("Improper number of arguments")
 | |
| 
 | |
| 	// TODO: possibly move this to ssh package
 | |
| 	baseSSHArgs = []string{
 | |
| 		"-o", "IdentitiesOnly=yes",
 | |
| 		"-o", "StrictHostKeyChecking=no",
 | |
| 		"-o", "UserKnownHostsFile=/dev/null",
 | |
| 		"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // HostInfo gives the mandatory information to connect to a host.
 | |
| type HostInfo interface {
 | |
| 	GetMachineName() string
 | |
| 
 | |
| 	GetIP() (string, error)
 | |
| 
 | |
| 	GetSSHUsername() string
 | |
| 
 | |
| 	GetSSHKeyPath() string
 | |
| }
 | |
| 
 | |
| // HostInfoLoader loads host information.
 | |
| type HostInfoLoader interface {
 | |
| 	load(name string) (HostInfo, error)
 | |
| }
 | |
| 
 | |
| type storeHostInfoLoader struct {
 | |
| 	store persist.Store
 | |
| }
 | |
| 
 | |
| func (s *storeHostInfoLoader) load(name string) (HostInfo, error) {
 | |
| 	host, err := s.store.Load(name)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("Error loading host: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	return host.Driver, nil
 | |
| }
 | |
| 
 | |
| func cmdScp(c CommandLine, api libmachine.API) error {
 | |
| 	args := c.Args()
 | |
| 	if len(args) != 2 {
 | |
| 		c.ShowHelp()
 | |
| 		return errWrongNumberArguments
 | |
| 	}
 | |
| 
 | |
| 	src := args[0]
 | |
| 	dest := args[1]
 | |
| 
 | |
| 	hostInfoLoader := &storeHostInfoLoader{api}
 | |
| 
 | |
| 	cmd, err := getScpCmd(src, dest, c.Bool("recursive"), hostInfoLoader)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return runCmdWithStdIo(*cmd)
 | |
| }
 | |
| 
 | |
| func getScpCmd(src, dest string, recursive bool, hostInfoLoader HostInfoLoader) (*exec.Cmd, error) {
 | |
| 	cmdPath, err := exec.LookPath("scp")
 | |
| 	if err != nil {
 | |
| 		return nil, errors.New("Error: You must have a copy of the scp binary locally to use the scp feature.")
 | |
| 	}
 | |
| 
 | |
| 	srcHost, srcPath, srcOpts, err := getInfoForScpArg(src, hostInfoLoader)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	destHost, destPath, destOpts, err := getInfoForScpArg(dest, hostInfoLoader)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// TODO: Check that "-3" flag is available in user's version of scp.
 | |
| 	// It is on every system I've checked, but the manual mentioned it's "newer"
 | |
| 	sshArgs := baseSSHArgs
 | |
| 	sshArgs = append(sshArgs, "-3")
 | |
| 	if recursive {
 | |
| 		sshArgs = append(sshArgs, "-r")
 | |
| 	}
 | |
| 
 | |
| 	// Append needed -i / private key flags to command.
 | |
| 	sshArgs = append(sshArgs, srcOpts...)
 | |
| 	sshArgs = append(sshArgs, destOpts...)
 | |
| 
 | |
| 	// Append actual arguments for the scp command (i.e. docker@<ip>:/path)
 | |
| 	locationArg, err := generateLocationArg(srcHost, srcPath)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	sshArgs = append(sshArgs, locationArg)
 | |
| 	locationArg, err = generateLocationArg(destHost, destPath)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	sshArgs = append(sshArgs, locationArg)
 | |
| 
 | |
| 	cmd := exec.Command(cmdPath, sshArgs...)
 | |
| 	log.Debug(*cmd)
 | |
| 	return cmd, nil
 | |
| }
 | |
| 
 | |
| func getInfoForScpArg(hostAndPath string, hostInfoLoader HostInfoLoader) (HostInfo, string, []string, error) {
 | |
| 	// Local path.  e.g. "/tmp/foo"
 | |
| 	if !strings.Contains(hostAndPath, ":") {
 | |
| 		return nil, hostAndPath, nil, nil
 | |
| 	}
 | |
| 
 | |
| 	// Path with hostname.  e.g. "hostname:/usr/bin/cmatrix"
 | |
| 	parts := strings.SplitN(hostAndPath, ":", 2)
 | |
| 	hostName := parts[0]
 | |
| 	path := parts[1]
 | |
| 	if hostName == "localhost" {
 | |
| 		return nil, path, nil, nil
 | |
| 	}
 | |
| 
 | |
| 	// Remote path
 | |
| 	hostInfo, err := hostInfoLoader.load(hostName)
 | |
| 	if err != nil {
 | |
| 		return nil, "", nil, fmt.Errorf("Error loading host: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	args := []string{
 | |
| 		"-i",
 | |
| 		hostInfo.GetSSHKeyPath(),
 | |
| 	}
 | |
| 	return hostInfo, path, args, nil
 | |
| }
 | |
| 
 | |
| func generateLocationArg(hostInfo HostInfo, path string) (string, error) {
 | |
| 	if hostInfo == nil {
 | |
| 		return path, nil
 | |
| 	}
 | |
| 
 | |
| 	ip, err := hostInfo.GetIP()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	location := fmt.Sprintf("%s@%s:%s", hostInfo.GetSSHUsername(), ip, path)
 | |
| 	return location, nil
 | |
| }
 | |
| 
 | |
| func runCmdWithStdIo(cmd exec.Cmd) error {
 | |
| 	cmd.Stdin = os.Stdin
 | |
| 	cmd.Stdout = os.Stdout
 | |
| 	cmd.Stderr = os.Stderr
 | |
| 
 | |
| 	return cmd.Run()
 | |
| }
 |