Skip to content

Commit 97890da

Browse files
committed
add rsync flag option to copy files using rsync
Signed-off-by: olalekan odukoya <odukoyaonline@gmail.com>
1 parent ca2c432 commit 97890da

File tree

3 files changed

+131
-21
lines changed

3 files changed

+131
-21
lines changed

cmd/limactl/copy.go

+127-21
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ Prefix guest filenames with the instance name and a colon.
2121
Example: limactl copy default:/etc/os-release .
2222
`
2323

24+
type copyTool string
25+
26+
const (
27+
rsync copyTool = "rsync"
28+
scp copyTool = "scp"
29+
)
30+
2431
func newCopyCommand() *cobra.Command {
2532
copyCommand := &cobra.Command{
2633
Use: "copy SOURCE ... TARGET",
@@ -49,13 +56,6 @@ func copyAction(cmd *cobra.Command, args []string) error {
4956
return err
5057
}
5158

52-
arg0, err := exec.LookPath("scp")
53-
if err != nil {
54-
return err
55-
}
56-
instances := make(map[string]*store.Instance)
57-
scpFlags := []string{}
58-
scpArgs := []string{}
5959
debug, err := cmd.Flags().GetBool("debug")
6060
if err != nil {
6161
return err
@@ -65,6 +65,45 @@ func copyAction(cmd *cobra.Command, args []string) error {
6565
verbose = true
6666
}
6767

68+
cpTool := rsync
69+
arg0, err := exec.LookPath(string(cpTool))
70+
if err != nil {
71+
arg0, err = exec.LookPath(string(cpTool))
72+
if err != nil {
73+
return err
74+
}
75+
}
76+
logrus.Infof("using copy tool %q", arg0)
77+
78+
var copyCmd *exec.Cmd
79+
switch cpTool {
80+
case scp:
81+
copyCmd, err = scpCommand(arg0, args, verbose, recursive)
82+
case rsync:
83+
copyCmd, err = rsyncCommand(arg0, args, verbose, recursive)
84+
default:
85+
err = fmt.Errorf("invalid copy tool %q", cpTool)
86+
}
87+
if err != nil {
88+
return err
89+
}
90+
91+
copyCmd.Stdin = cmd.InOrStdin()
92+
copyCmd.Stdout = cmd.OutOrStdout()
93+
copyCmd.Stderr = cmd.ErrOrStderr()
94+
logrus.Debugf("executing %v (may take a long time)", copyCmd)
95+
96+
// TODO: use syscall.Exec directly (results in losing tty?)
97+
return copyCmd.Run()
98+
}
99+
100+
func scpCommand(command string, args []string, verbose, recursive bool) (*exec.Cmd, error) {
101+
instances := make(map[string]*store.Instance)
102+
103+
scpFlags := []string{}
104+
scpArgs := []string{}
105+
var err error
106+
68107
if verbose {
69108
scpFlags = append(scpFlags, "-v")
70109
} else {
@@ -74,6 +113,7 @@ func copyAction(cmd *cobra.Command, args []string) error {
74113
if recursive {
75114
scpFlags = append(scpFlags, "-r")
76115
}
116+
77117
// this assumes that ssh and scp come from the same place, but scp has no -V
78118
legacySSH := sshutil.DetectOpenSSHVersion("ssh").LessThan(*semver.New("8.0.0"))
79119
for _, arg := range args {
@@ -86,12 +126,12 @@ func copyAction(cmd *cobra.Command, args []string) error {
86126
inst, err := store.Inspect(instName)
87127
if err != nil {
88128
if errors.Is(err, os.ErrNotExist) {
89-
return fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName)
129+
return nil, fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName)
90130
}
91-
return err
131+
return nil, err
92132
}
93133
if inst.Status == store.StatusStopped {
94-
return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName)
134+
return nil, fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName)
95135
}
96136
if legacySSH {
97137
scpFlags = append(scpFlags, "-P", fmt.Sprintf("%d", inst.SSHLocalPort))
@@ -101,11 +141,11 @@ func copyAction(cmd *cobra.Command, args []string) error {
101141
}
102142
instances[instName] = inst
103143
default:
104-
return fmt.Errorf("path %q contains multiple colons", arg)
144+
return nil, fmt.Errorf("path %q contains multiple colons", arg)
105145
}
106146
}
107147
if legacySSH && len(instances) > 1 {
108-
return errors.New("more than one (instance) host is involved in this command, this is only supported for openSSH v8.0 or higher")
148+
return nil, errors.New("more than one (instance) host is involved in this command, this is only supported for openSSH v8.0 or higher")
109149
}
110150
scpFlags = append(scpFlags, "-3", "--")
111151
scpArgs = append(scpFlags, scpArgs...)
@@ -118,24 +158,90 @@ func copyAction(cmd *cobra.Command, args []string) error {
118158
for _, inst := range instances {
119159
sshOpts, err = sshutil.SSHOpts("ssh", inst.Dir, *inst.Config.User.Name, false, false, false, false)
120160
if err != nil {
121-
return err
161+
return nil, err
122162
}
123163
}
124164
} else {
125165
// Copying among multiple hosts; we can't pass in host-specific options.
126166
sshOpts, err = sshutil.CommonOpts("ssh", false)
127167
if err != nil {
128-
return err
168+
return nil, err
129169
}
130170
}
131171
sshArgs := sshutil.SSHArgsFromOpts(sshOpts)
132172

133-
sshCmd := exec.Command(arg0, append(sshArgs, scpArgs...)...)
134-
sshCmd.Stdin = cmd.InOrStdin()
135-
sshCmd.Stdout = cmd.OutOrStdout()
136-
sshCmd.Stderr = cmd.ErrOrStderr()
137-
logrus.Debugf("executing scp (may take a long time): %+v", sshCmd.Args)
173+
return exec.Command(command, append(sshArgs, scpArgs...)...), nil
174+
}
138175

139-
// TODO: use syscall.Exec directly (results in losing tty?)
140-
return sshCmd.Run()
176+
func rsyncCommand(command string, args []string, verbose, recursive bool) (*exec.Cmd, error) {
177+
instances := make(map[string]*store.Instance)
178+
179+
var instName string
180+
181+
rsyncFlags := []string{}
182+
rsyncArgs := []string{}
183+
184+
if verbose {
185+
rsyncFlags = append(rsyncFlags, "-v", "--progress")
186+
} else {
187+
rsyncFlags = append(rsyncFlags, "-q")
188+
}
189+
190+
if recursive {
191+
rsyncFlags = append(rsyncFlags, "-r")
192+
}
193+
194+
for _, arg := range args {
195+
path := strings.Split(arg, ":")
196+
switch len(path) {
197+
case 1:
198+
inst, ok := instances[instName]
199+
if !ok {
200+
return nil, fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName)
201+
}
202+
guestVM := fmt.Sprintf("%s@127.0.0.1:%s", *inst.Config.User.Name, path[0])
203+
rsyncArgs = append(rsyncArgs, guestVM)
204+
case 2:
205+
instName = path[0]
206+
inst, err := store.Inspect(instName)
207+
if err != nil {
208+
if errors.Is(err, os.ErrNotExist) {
209+
return nil, fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName)
210+
}
211+
return nil, err
212+
}
213+
sshOpts, err := sshutil.SSHOpts("ssh", inst.Dir, *inst.Config.User.Name, false, false, false, false)
214+
if err != nil {
215+
return nil, err
216+
}
217+
218+
sshArgs := sshutil.SSHArgsFromOpts(sshOpts)
219+
sshStr := fmt.Sprintf("ssh -p %s %s", fmt.Sprintf("%d", inst.SSHLocalPort), strings.Join(sshArgs, " "))
220+
221+
destDir := args[1]
222+
mkdirCmd := exec.Command(
223+
"ssh",
224+
"-p", fmt.Sprintf("%d", inst.SSHLocalPort),
225+
)
226+
mkdirCmd.Args = append(mkdirCmd.Args, sshArgs...)
227+
mkdirCmd.Args = append(mkdirCmd.Args,
228+
fmt.Sprintf("%s@%s", *inst.Config.User.Name, "127.0.0.1"),
229+
fmt.Sprintf("sudo mkdir -p %q && sudo chown %s:%s %s", destDir, *inst.Config.User.Name, *inst.Config.User.Name, destDir),
230+
)
231+
mkdirCmd.Stdout = os.Stdout
232+
mkdirCmd.Stderr = os.Stderr
233+
if err := mkdirCmd.Run(); err != nil {
234+
return nil, fmt.Errorf("failed to create directory %q on remote: %w", destDir, err)
235+
}
236+
237+
rsyncArgs = append(rsyncArgs, "-avz", "-e", sshStr, path[1])
238+
instances[instName] = inst
239+
default:
240+
return nil, fmt.Errorf("path %q contains multiple colons", arg)
241+
}
242+
}
243+
244+
rsyncArgs = append(rsyncFlags, rsyncArgs...)
245+
246+
return exec.Command(command, rsyncArgs...), nil
141247
}

pkg/cidata/cidata.TEMPLATE.d/user-data

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ package_upgrade: true
1111
package_reboot_if_required: true
1212
{{- end }}
1313

14+
packages:
15+
- rsync
16+
1417
{{- if or .RosettaEnabled (or (eq .MountType "9p") (eq .MountType "virtiofs")) }}
1518
mounts:
1619
{{- if .RosettaEnabled }}{{/* Mount the rosetta volume before systemd-binfmt.service(8) starts */}}

pkg/hostagent/hostagent.go

+1
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ func (a *HostAgent) startHostAgentRoutines(ctx context.Context) error {
439439
if err := a.waitForRequirements("essential", a.essentialRequirements()); err != nil {
440440
errs = append(errs, err)
441441
}
442+
442443
if *a.instConfig.SSH.ForwardAgent {
443444
faScript := `#!/bin/bash
444445
set -eux -o pipefail

0 commit comments

Comments
 (0)