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 }