@@ -21,6 +21,13 @@ Prefix guest filenames with the instance name and a colon.
21
21
Example: limactl copy default:/etc/os-release .
22
22
`
23
23
24
+ type copyTool string
25
+
26
+ const (
27
+ Rsync copyTool = "rsync"
28
+ Scp copyTool = "scp"
29
+ )
30
+
24
31
func newCopyCommand () * cobra.Command {
25
32
copyCommand := & cobra.Command {
26
33
Use : "copy SOURCE ... TARGET" ,
@@ -49,13 +56,20 @@ func copyAction(cmd *cobra.Command, args []string) error {
49
56
return err
50
57
}
51
58
52
- arg0 , err := exec .LookPath ("scp" )
53
- if err != nil {
54
- return err
59
+ defaultTool := Rsync
60
+ arg0 , err := exec .LookPath (string (defaultTool ))
61
+ if err != nil || ! strings .HasSuffix (arg0 , "rsync" ) {
62
+ defaultTool = Scp
63
+ arg0 , err = exec .LookPath (string (defaultTool ))
64
+ if err != nil {
65
+ return err
66
+ }
55
67
}
68
+ logrus .Infof ("using copy tool %q" , arg0 )
69
+
56
70
instances := make (map [string ]* store.Instance )
57
- scpFlags := []string {}
58
- scpArgs := []string {}
71
+ copyToolFlags := []string {}
72
+ copyToolArgs := []string {}
59
73
debug , err := cmd .Flags ().GetBool ("debug" )
60
74
if err != nil {
61
75
return err
@@ -65,22 +79,28 @@ func copyAction(cmd *cobra.Command, args []string) error {
65
79
verbose = true
66
80
}
67
81
82
+ useRsync := isCopyToolRsync (defaultTool )
83
+
68
84
if verbose {
69
- scpFlags = append (scpFlags , "-v" )
70
- } else {
71
- scpFlags = append (scpFlags , "-q" )
85
+ copyToolFlags = append (copyToolFlags , "-v" )
86
+ if useRsync {
87
+ copyToolFlags = append (copyToolFlags , "--progress" )
88
+ }
89
+ }
90
+ if ! verbose {
91
+ copyToolFlags = append (copyToolFlags , "-q" )
72
92
}
73
93
74
94
if recursive {
75
- scpFlags = append (scpFlags , "-r" )
95
+ copyToolFlags = append (copyToolFlags , "-r" )
76
96
}
77
97
// this assumes that ssh and scp come from the same place, but scp has no -V
78
98
legacySSH := sshutil .DetectOpenSSHVersion ("ssh" ).LessThan (* semver .New ("8.0.0" ))
79
99
for _ , arg := range args {
80
100
path := strings .Split (arg , ":" )
81
101
switch len (path ) {
82
102
case 1 :
83
- scpArgs = append (scpArgs , arg )
103
+ copyToolArgs = append (copyToolArgs , arg )
84
104
case 2 :
85
105
instName := path [0 ]
86
106
inst , err := store .Inspect (instName )
@@ -93,11 +113,15 @@ func copyAction(cmd *cobra.Command, args []string) error {
93
113
if inst .Status == store .StatusStopped {
94
114
return fmt .Errorf ("instance %q is stopped, run `limactl start %s` to start the instance" , instName , instName )
95
115
}
96
- if legacySSH {
97
- scpFlags = append (scpFlags , "-P" , fmt .Sprintf ("%d" , inst .SSHLocalPort ))
98
- scpArgs = append (scpArgs , fmt .Sprintf ("%s@127.0.0.1:%s" , * inst .Config .User .Name , path [1 ]))
116
+ if useRsync {
117
+ copyToolArgs = append (copyToolArgs , fmt .Sprintf ("%s@127.0.0.1:%s" , * inst .Config .User .Name , path [1 ]))
99
118
} else {
100
- scpArgs = append (scpArgs , fmt .Sprintf ("scp://%s@127.0.0.1:%d/%s" , * inst .Config .User .Name , inst .SSHLocalPort , path [1 ]))
119
+ if legacySSH {
120
+ copyToolFlags = append (copyToolFlags , "-P" , fmt .Sprintf ("%d" , inst .SSHLocalPort ))
121
+ copyToolArgs = append (copyToolArgs , fmt .Sprintf ("%s@127.0.0.1:%s" , * inst .Config .User .Name , path [1 ]))
122
+ } else {
123
+ copyToolArgs = append (copyToolArgs , fmt .Sprintf ("scp://%s@127.0.0.1:%d/%s" , * inst .Config .User .Name , inst .SSHLocalPort , path [1 ]))
124
+ }
101
125
}
102
126
instances [instName ] = inst
103
127
default :
@@ -107,8 +131,10 @@ func copyAction(cmd *cobra.Command, args []string) error {
107
131
if legacySSH && len (instances ) > 1 {
108
132
return errors .New ("more than one (instance) host is involved in this command, this is only supported for openSSH v8.0 or higher" )
109
133
}
110
- scpFlags = append (scpFlags , "-3" , "--" )
111
- scpArgs = append (scpFlags , scpArgs ... )
134
+ if ! useRsync {
135
+ copyToolFlags = append (copyToolFlags , "-3" , "--" )
136
+ }
137
+ copyToolArgs = append (copyToolFlags , copyToolArgs ... )
112
138
113
139
var sshOpts []string
114
140
if len (instances ) == 1 {
@@ -128,14 +154,28 @@ func copyAction(cmd *cobra.Command, args []string) error {
128
154
return err
129
155
}
130
156
}
157
+
131
158
sshArgs := sshutil .SSHArgsFromOpts (sshOpts )
132
159
133
- sshCmd := exec .Command (arg0 , append (sshArgs , scpArgs ... )... )
160
+ sshCmd := exec .Command (arg0 , createArgs (sshArgs , copyToolArgs , defaultTool )... )
134
161
sshCmd .Stdin = cmd .InOrStdin ()
135
162
sshCmd .Stdout = cmd .OutOrStdout ()
136
163
sshCmd .Stderr = cmd .ErrOrStderr ()
137
- logrus .Debugf ("executing scp (may take a long time): %+v" , sshCmd .Args )
164
+ logrus .Debugf ("executing %s (may take a long time): %+v" , arg0 , sshCmd .Args )
138
165
139
166
// TODO: use syscall.Exec directly (results in losing tty?)
140
167
return sshCmd .Run ()
141
168
}
169
+
170
+ func isCopyToolRsync (copyTool copyTool ) bool {
171
+ return copyTool == Rsync
172
+ }
173
+
174
+ func createArgs (sshArgs , copyToolArgs []string , copyTool copyTool ) []string {
175
+ if isCopyToolRsync (copyTool ) {
176
+ rsyncFlags := []string {"-e" , fmt .Sprintf ("ssh %s" , strings .Join (sshArgs , " " ))}
177
+ return append (rsyncFlags , copyToolArgs ... )
178
+ }
179
+
180
+ return append (sshArgs , copyToolArgs ... )
181
+ }
0 commit comments