github.com/scaleway/scaleway-cli@v1.11.1/pkg/commands/cp.go (about)

     1  // Copyright (C) 2015 Scaleway. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE.md file.
     4  
     5  package commands
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/Sirupsen/logrus"
    16  	"github.com/docker/docker/pkg/archive"
    17  	"github.com/scaleway/scaleway-cli/pkg/api"
    18  	"github.com/scaleway/scaleway-cli/pkg/utils"
    19  )
    20  
    21  // CpArgs are arguments passed to `RunCp`
    22  type CpArgs struct {
    23  	Gateway     string
    24  	Source      string
    25  	Destination string
    26  	SSHUser     string
    27  	SSHPort     int
    28  }
    29  
    30  // RunCp is the handler for 'scw cp'
    31  func RunCp(ctx CommandContext, args CpArgs) error {
    32  	if strings.Count(args.Source, ":") > 1 || strings.Count(args.Destination, ":") > 1 {
    33  		return fmt.Errorf("bad usage, see 'scw help cp'")
    34  	}
    35  
    36  	sourceStream, err := TarFromSource(ctx, args.Source, args.Gateway, args.SSHUser, args.SSHPort)
    37  	if err != nil {
    38  		return fmt.Errorf("cannot tar from source '%s': %v", args.Source, err)
    39  	}
    40  
    41  	err = UntarToDest(ctx, sourceStream, args.Destination, args.Gateway, args.SSHUser, args.SSHPort)
    42  	if err != nil {
    43  		return fmt.Errorf("cannot untar to destination '%s': %v", args.Destination, err)
    44  	}
    45  	return nil
    46  }
    47  
    48  // TarFromSource creates a stream buffer with the tarballed content of the user source
    49  func TarFromSource(ctx CommandContext, source, gateway, user string, port int) (*io.ReadCloser, error) {
    50  	var tarOutputStream io.ReadCloser
    51  
    52  	// source is a server address + path (scp-like uri)
    53  	if strings.Contains(source, ":") {
    54  		logrus.Debugf("Creating a tarball remotely and streaming it using SSH")
    55  		serverParts := strings.Split(source, ":")
    56  		if len(serverParts) != 2 {
    57  			return nil, fmt.Errorf("invalid source uri, see 'scw cp -h' for usage")
    58  		}
    59  
    60  		serverID, err := ctx.API.GetServerID(serverParts[0])
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  
    65  		server, err := ctx.API.GetServer(serverID)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		dir, base := utils.PathToTARPathparts(serverParts[1])
    71  		logrus.Debugf("Equivalent to 'scp root@%s:%s/%s ...'", server.PublicAddress.IP, dir, base)
    72  
    73  		// remoteCommand is executed on the remote server
    74  		// it streams a tarball raw content
    75  		remoteCommand := []string{"tar"}
    76  		remoteCommand = append(remoteCommand, "-C", dir)
    77  		if ctx.Getenv("DEBUG") == "1" {
    78  			remoteCommand = append(remoteCommand, "-v")
    79  		}
    80  		remoteCommand = append(remoteCommand, "-cf", "-")
    81  		remoteCommand = append(remoteCommand, base)
    82  
    83  		// Resolve gateway
    84  		if gateway == "" {
    85  			gateway = ctx.Getenv("SCW_GATEWAY")
    86  		}
    87  
    88  		if gateway == serverID || gateway == serverParts[0] {
    89  			gateway = ""
    90  		} else {
    91  			gateway, err = api.ResolveGateway(ctx.API, gateway)
    92  			if err != nil {
    93  				return nil, fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err)
    94  			}
    95  		}
    96  
    97  		// execCmd contains the ssh connection + the remoteCommand
    98  		sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, user, port, false, remoteCommand, gateway)
    99  		logrus.Debugf("Executing: %s", sshCommand)
   100  		spawnSrc := exec.Command("ssh", sshCommand.Slice()[1:]...)
   101  
   102  		tarOutputStream, err = spawnSrc.StdoutPipe()
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		tarErrorStream, err := spawnSrc.StderrPipe()
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  		defer tarErrorStream.Close()
   112  		io.Copy(ctx.Stderr, tarErrorStream)
   113  
   114  		err = spawnSrc.Start()
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		defer spawnSrc.Wait()
   119  
   120  		return &tarOutputStream, nil
   121  	}
   122  
   123  	// source is stdin
   124  	if source == "-" {
   125  		logrus.Debugf("Streaming tarball from stdin")
   126  		// FIXME: should be ctx.Stdin
   127  		tarOutputStream = os.Stdin
   128  		return &tarOutputStream, nil
   129  	}
   130  
   131  	// source is a path on localhost
   132  	logrus.Debugf("Taring local path %s", source)
   133  	path, err := filepath.Abs(source)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	path, err = filepath.EvalSymlinks(path)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	logrus.Debugf("Real local path is %s", path)
   142  
   143  	dir, base := utils.PathToTARPathparts(path)
   144  
   145  	tarOutputStream, err = archive.TarWithOptions(dir, &archive.TarOptions{
   146  		Compression:  archive.Uncompressed,
   147  		IncludeFiles: []string{base},
   148  	})
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	return &tarOutputStream, nil
   153  }
   154  
   155  // UntarToDest writes to user destination the streamed tarball in input
   156  func UntarToDest(ctx CommandContext, sourceStream *io.ReadCloser, destination, gateway, user string, port int) error {
   157  	// destination is a server address + path (scp-like uri)
   158  	if strings.Contains(destination, ":") {
   159  		logrus.Debugf("Streaming using ssh and untaring remotely")
   160  		serverParts := strings.Split(destination, ":")
   161  		if len(serverParts) != 2 {
   162  			return fmt.Errorf("invalid destination uri, see 'scw cp -h' for usage")
   163  		}
   164  
   165  		serverID, err := ctx.API.GetServerID(serverParts[0])
   166  		if err != nil {
   167  			return err
   168  		}
   169  
   170  		server, err := ctx.API.GetServer(serverID)
   171  		if err != nil {
   172  			return err
   173  		}
   174  
   175  		// remoteCommand is executed on the remote server
   176  		// it streams a tarball raw content
   177  		remoteCommand := []string{"tar"}
   178  		remoteCommand = append(remoteCommand, "-C", serverParts[1])
   179  		if ctx.Getenv("DEBUG") == "1" {
   180  			remoteCommand = append(remoteCommand, "-v")
   181  		}
   182  		remoteCommand = append(remoteCommand, "-xf", "-")
   183  
   184  		// Resolve gateway
   185  		if gateway == "" {
   186  			gateway = ctx.Getenv("SCW_GATEWAY")
   187  		}
   188  		if gateway == serverID || gateway == serverParts[0] {
   189  			gateway = ""
   190  		} else {
   191  			gateway, err = api.ResolveGateway(ctx.API, gateway)
   192  			if err != nil {
   193  				return fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err)
   194  			}
   195  		}
   196  
   197  		// execCmd contains the ssh connection + the remoteCommand
   198  		sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, user, port, false, remoteCommand, gateway)
   199  		logrus.Debugf("Executing: %s", sshCommand)
   200  		spawnDst := exec.Command("ssh", sshCommand.Slice()[1:]...)
   201  
   202  		untarInputStream, err := spawnDst.StdinPipe()
   203  		if err != nil {
   204  			return err
   205  		}
   206  		defer untarInputStream.Close()
   207  
   208  		// spawnDst.Stderr = ctx.Stderr
   209  		// spawnDst.Stdout = ctx.Stdout
   210  
   211  		err = spawnDst.Start()
   212  		if err != nil {
   213  			return err
   214  		}
   215  
   216  		_, err = io.Copy(untarInputStream, *sourceStream)
   217  		return err
   218  	}
   219  
   220  	// destination is stdout
   221  	if destination == "-" { // stdout
   222  		logrus.Debugf("Writing sourceStream(%v) to ctx.Stdout(%v)", sourceStream, ctx.Stdout)
   223  		_, err := io.Copy(ctx.Stdout, *sourceStream)
   224  		return err
   225  	}
   226  
   227  	// destination is a path on localhost
   228  	logrus.Debugf("Untaring to local path: %s", destination)
   229  	err := archive.Untar(*sourceStream, destination, &archive.TarOptions{NoLchown: true})
   230  	return err
   231  }