1
1
package main
2
2
3
3
import (
4
+ "fmt"
4
5
"io"
5
6
"os"
6
7
"os/exec"
7
- "path"
8
+ "path/filepath "
8
9
"strings"
9
10
10
11
log "github.com/Sirupsen/logrus"
@@ -13,9 +14,23 @@ import (
13
14
14
15
var cmdCp = & Command {
15
16
Exec : runCp ,
16
- UsageLine : "cp [OPTIONS] SERVER:PATH HOSTDIR |-" ,
17
+ UsageLine : "cp [OPTIONS] SERVER:PATH|HOSTPATH|- SERVER:PATH|HOSTPATH |-" ,
17
18
Description : "Copy files/folders from a PATH on the server to a HOSTDIR on the host" ,
18
19
Help : "Copy files/folders from a PATH on the server to a HOSTDIR on the host\n running the command. Use '-' to write the data as a tar file to STDOUT." ,
20
+ Examples : `
21
+ $ scw cp path/to/my/local/file myserver:path
22
+ $ scw cp myserver:path/to/file path/to/my/local/dir
23
+ $ scw cp myserver:path/to/file myserver2:path/to/dir
24
+ $ scw cp myserver:path/to/file - > myserver-pathtofile-backup.tar
25
+ $ scw cp myserver:path/to/file - | tar -tvf -
26
+ $ scw cp path/to/my/local/dir myserver:path
27
+ $ scw cp myserver:path/to/dir path/to/my/local/dir
28
+ $ scw cp myserver:path/to/dir myserver2:path/to/dir
29
+ $ scw cp myserver:path/to/dir - > myserver-pathtodir-backup.tar
30
+ $ scw cp myserver:path/to/dir - | tar -tvf -
31
+ $ cat archive.tar | scw cp - myserver:/path
32
+ $ tar -cvf - . | scw cp - myserver:path
33
+ ` ,
19
34
}
20
35
21
36
func init () {
@@ -25,72 +40,174 @@ func init() {
25
40
// Flags
26
41
var cpHelp bool // -h, --help flag
27
42
28
- func runCp (cmd * Command , args []string ) {
29
- if cpHelp {
30
- cmd .PrintUsage ()
31
- }
32
- if len (args ) != 2 {
33
- cmd .PrintShortUsage ()
34
- }
43
+ func TarFromSource (api * ScalewayAPI , source string ) (* io.ReadCloser , error ) {
44
+ var tarOutputStream io.ReadCloser
35
45
36
- hostPath := args [1 ]
46
+ // source is a server address + path (scp-like uri)
47
+ if strings .Index (source , ":" ) > - 1 {
48
+ log .Debugf ("Creating a tarball remotely and streaming it using SSH" )
49
+ serverParts := strings .Split (source , ":" )
50
+ if len (serverParts ) != 2 {
51
+ return nil , fmt .Errorf ("invalid source uri, see 'scw cp -h' for usage" )
52
+ }
37
53
38
- serverParts := strings .Split (args [0 ], ":" )
39
- if len (serverParts ) != 2 {
40
- log .Fatalf ("usage: scw %s" , cmd .UsageLine )
41
- }
54
+ serverID := api .GetServerID (serverParts [0 ])
42
55
43
- serverID := cmd .API .GetServerID (serverParts [0 ])
56
+ server , err := api .GetServer (serverID )
57
+ if err != nil {
58
+ return nil , err
59
+ }
44
60
45
- server , err := cmd .API .GetServer (serverID )
46
- if err != nil {
47
- log .Fatalf ("Failed to get server information for %s: %v" , serverID , err )
48
- }
61
+ dir , base := PathToTARPathparts (serverParts [1 ])
62
+ log .Debugf ("Equivalent to 'scp root@%s:%s/%s ...'" , server .PublicAddress .IP , dir , base )
63
+
64
+ // remoteCommand is executed on the remote server
65
+ // it streams a tarball raw content
66
+ remoteCommand := []string {"tar" }
67
+ remoteCommand = append (remoteCommand , "-C" , dir )
68
+ if os .Getenv ("DEBUG" ) == "1" {
69
+ remoteCommand = append (remoteCommand , "-v" )
70
+ }
71
+ remoteCommand = append (remoteCommand , "-cf" , "-" )
72
+ remoteCommand = append (remoteCommand , base )
73
+
74
+ // execCmd contains the ssh connection + the remoteCommand
75
+ execCmd := append (NewSSHExecCmd (server .PublicAddress .IP , false , remoteCommand ))
76
+ log .Debugf ("Executing: ssh %s" , strings .Join (execCmd , " " ))
77
+ spawnSrc := exec .Command ("ssh" , execCmd ... )
78
+
79
+ tarOutputStream , err = spawnSrc .StdoutPipe ()
80
+ if err != nil {
81
+ return nil , err
82
+ }
83
+
84
+ tarErrorStream , err := spawnSrc .StderrPipe ()
85
+ if err != nil {
86
+ return nil , err
87
+ }
88
+ defer tarErrorStream .Close ()
89
+ io .Copy (os .Stderr , tarErrorStream )
49
90
50
- // remoteCommand is executed on the remote server
51
- // it streams a tarball raw content
52
- remoteCommand := []string {"tar" }
53
- remoteCommand = append (remoteCommand , "-C" , path .Dir (serverParts [1 ]))
54
- if os .Getenv ("DEBUG" ) == "1" {
55
- remoteCommand = append (remoteCommand , "-v" )
91
+ err = spawnSrc .Start ()
92
+ if err != nil {
93
+ return nil , err
94
+ }
95
+ defer spawnSrc .Wait ()
96
+
97
+ return & tarOutputStream , nil
56
98
}
57
- remoteCommand = append (remoteCommand , "-cf" , "-" )
58
- remoteCommand = append (remoteCommand , path .Base (serverParts [1 ]))
59
99
60
- // execCmd contains the ssh connection + the remoteCommand
61
- execCmd := append (NewSSHExecCmd (server .PublicAddress .IP , false , remoteCommand ))
62
- log .Debugf ("Executing: ssh %s" , strings .Join (execCmd , " " ))
63
- spawn := exec .Command ("ssh" , execCmd ... )
100
+ // source is stdin
101
+ if source == "-" {
102
+ log .Debugf ("Streaming tarball from stdin" )
103
+ tarOutputStream = os .Stdin
104
+ return & tarOutputStream , nil
105
+ }
64
106
65
- tarOutputStream , err := spawn .StdoutPipe ()
107
+ // source is a path on localhost
108
+ log .Debugf ("Taring local path %s" , source )
109
+ path , err := filepath .Abs (source )
66
110
if err != nil {
67
- log . Fatal ( err )
111
+ return nil , err
68
112
}
69
- tarErrorStream , err := spawn . StderrPipe ( )
113
+ path , err = filepath . EvalSymlinks ( path )
70
114
if err != nil {
71
- log . Fatal ( err )
115
+ return nil , err
72
116
}
117
+ log .Debugf ("Real local path is %s" , path )
73
118
74
- err = spawn .Start ()
119
+ dir , base := PathToTARPathparts (path )
120
+
121
+ tarOutputStream , err = archive .TarWithOptions (dir , & archive.TarOptions {
122
+ Compression : archive .Uncompressed ,
123
+ IncludeFiles : []string {base },
124
+ })
75
125
if err != nil {
76
- log . Fatalf ( "Failed to start ssh command: %v" , err )
126
+ return nil , err
77
127
}
128
+ return & tarOutputStream , nil
129
+ }
130
+
131
+ func UntarToDest (api * ScalewayAPI , sourceStream * io.ReadCloser , destination string ) error {
132
+ // destination is a server address + path (scp-like uri)
133
+ if strings .Index (destination , ":" ) > - 1 {
134
+ log .Debugf ("Streaming using ssh and untaring remotely" )
135
+ serverParts := strings .Split (destination , ":" )
136
+ if len (serverParts ) != 2 {
137
+ return fmt .Errorf ("invalid destination uri, see 'scw cp -h' for usage" )
138
+ }
139
+
140
+ serverID := api .GetServerID (serverParts [0 ])
141
+
142
+ server , err := api .GetServer (serverID )
143
+ if err != nil {
144
+ return err
145
+ }
78
146
79
- defer spawn .Wait ()
147
+ // remoteCommand is executed on the remote server
148
+ // it streams a tarball raw content
149
+ remoteCommand := []string {"tar" }
150
+ remoteCommand = append (remoteCommand , "-C" , serverParts [1 ])
151
+ if os .Getenv ("DEBUG" ) == "1" {
152
+ remoteCommand = append (remoteCommand , "-v" )
153
+ }
154
+ remoteCommand = append (remoteCommand , "-xf" , "-" )
80
155
81
- io .Copy (os .Stderr , tarErrorStream )
156
+ // execCmd contains the ssh connection + the remoteCommand
157
+ execCmd := append (NewSSHExecCmd (server .PublicAddress .IP , false , remoteCommand ))
158
+ log .Debugf ("Executing: ssh %s" , strings .Join (execCmd , " " ))
159
+ spawnDst := exec .Command ("ssh" , execCmd ... )
82
160
83
- if hostPath == "-" {
84
- log .Debugf ("Writing tarOutputStream(%v) to os.Stdout(%v)" , tarOutputStream , os .Stdout )
85
- written , err := io .Copy (os .Stdout , tarOutputStream )
86
- log .Debugf ("%d bytes written" , written )
161
+ untarInputStream , err := spawnDst .StdinPipe ()
87
162
if err != nil {
88
- log . Fatal ( err )
163
+ return err
89
164
}
90
- } else {
91
- err = archive .Untar (tarOutputStream , hostPath , & archive.TarOptions {NoLchown : true })
165
+ defer untarInputStream .Close ()
166
+
167
+ // spawnDst.Stderr = os.Stderr
168
+ // spawnDst.Stdout = os.Stdout
169
+
170
+ err = spawnDst .Start ()
92
171
if err != nil {
93
- log . Fatalf ( "Failed to untar the remote archive: %v" , err )
172
+ return err
94
173
}
174
+
175
+ _ , err = io .Copy (untarInputStream , * sourceStream )
176
+ return err
177
+ }
178
+
179
+ // destination is stdout
180
+ if destination == "-" { // stdout
181
+ log .Debugf ("Writing sourceStream(%v) to os.Stdout(%v)" , sourceStream , os .Stdout )
182
+ _ , err := io .Copy (os .Stdout , * sourceStream )
183
+ return err
184
+ }
185
+
186
+ // destination is a path on localhost
187
+ log .Debugf ("Untaring to local path: %s" , destination )
188
+ err := archive .Untar (* sourceStream , destination , & archive.TarOptions {NoLchown : true })
189
+ return err
190
+ }
191
+
192
+ func runCp (cmd * Command , args []string ) {
193
+ if cpHelp {
194
+ cmd .PrintUsage ()
195
+ }
196
+ if len (args ) != 2 {
197
+ cmd .PrintShortUsage ()
198
+ }
199
+
200
+ if strings .Count (args [0 ], ":" ) > 1 || strings .Count (args [1 ], ":" ) > 1 {
201
+ log .Fatalf ("usage: scw %s" , cmd .UsageLine )
202
+ }
203
+
204
+ sourceStream , err := TarFromSource (cmd .API , args [0 ])
205
+ if err != nil {
206
+ log .Fatalf ("Cannot tar from source '%s': %v" , args [0 ], err )
207
+ }
208
+
209
+ err = UntarToDest (cmd .API , sourceStream , args [1 ])
210
+ if err != nil {
211
+ log .Fatalf ("Cannot untar to destination '%s': %v" , args [1 ], err )
95
212
}
96
213
}
0 commit comments