Skip to content

Commit ab942da

Browse files
authored
Merge pull request #2028 from afbjorklund/guest-install
Add hidden command to install the guest components
2 parents a4061ff + 76caa6b commit ab942da

File tree

4 files changed

+224
-37
lines changed

4 files changed

+224
-37
lines changed

cmd/limactl/guest-install.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"bytes"
8+
"compress/gzip"
9+
"context"
10+
"errors"
11+
"fmt"
12+
"io"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
17+
"github.com/sirupsen/logrus"
18+
"github.com/spf13/cobra"
19+
20+
"github.com/lima-vm/lima/v2/pkg/cacheutil"
21+
"github.com/lima-vm/lima/v2/pkg/limatype"
22+
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
23+
"github.com/lima-vm/lima/v2/pkg/store"
24+
"github.com/lima-vm/lima/v2/pkg/usrlocalsharelima"
25+
)
26+
27+
func newGuestInstallCommand() *cobra.Command {
28+
guestInstallCommand := &cobra.Command{
29+
Use: "guest-install INSTANCE",
30+
Short: "Install guest components",
31+
Args: WrapArgsError(cobra.MaximumNArgs(1)),
32+
RunE: guestInstallAction,
33+
ValidArgsFunction: cobra.NoFileCompletions,
34+
Hidden: true,
35+
}
36+
return guestInstallCommand
37+
}
38+
39+
func runCmd(ctx context.Context, name string, flags []string, args ...string) error {
40+
cmd := exec.CommandContext(ctx, name, append(flags, args...)...)
41+
cmd.Stdout = os.Stdout
42+
cmd.Stderr = os.Stderr
43+
logrus.Debugf("executing %v", cmd.Args)
44+
return cmd.Run()
45+
}
46+
47+
func shell(ctx context.Context, name string, flags []string, args ...string) (string, error) {
48+
cmd := exec.CommandContext(ctx, name, append(flags, args...)...)
49+
out, err := cmd.Output()
50+
if err != nil {
51+
return "", err
52+
}
53+
out = bytes.TrimSuffix(out, []byte{'\n'})
54+
return string(out), nil
55+
}
56+
57+
func guestInstallAction(cmd *cobra.Command, args []string) error {
58+
instName := DefaultInstanceName
59+
if len(args) > 0 {
60+
instName = args[0]
61+
}
62+
63+
inst, err := store.Inspect(cmd.Context(), instName)
64+
if err != nil {
65+
return err
66+
}
67+
if inst.Status == limatype.StatusStopped {
68+
return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName)
69+
}
70+
71+
ctx := cmd.Context()
72+
73+
sshExe := "ssh"
74+
sshConfig := filepath.Join(inst.Dir, filenames.SSHConfig)
75+
sshFlags := []string{"-F", sshConfig}
76+
77+
scpExe := "scp"
78+
scpFlags := sshFlags
79+
80+
hostname := fmt.Sprintf("lima-%s", inst.Name)
81+
prefix := *inst.Config.GuestInstallPrefix
82+
83+
// lima-guestagent
84+
guestAgentBinary, err := usrlocalsharelima.GuestAgentBinary(*inst.Config.OS, *inst.Config.Arch)
85+
if err != nil {
86+
return err
87+
}
88+
guestAgentFilename := filepath.Base(guestAgentBinary)
89+
if _, err := os.Stat(guestAgentBinary); err != nil {
90+
if !errors.Is(err, os.ErrNotExist) {
91+
return err
92+
}
93+
compressedGuestAgent, err := os.Open(guestAgentBinary + ".gz")
94+
if err != nil {
95+
return err
96+
}
97+
defer compressedGuestAgent.Close()
98+
tmpGuestAgent, err := os.CreateTemp("", "lima-guestagent-")
99+
if err != nil {
100+
return err
101+
}
102+
logrus.Debugf("Decompressing %s.gz", guestAgentBinary)
103+
guestAgent, err := gzip.NewReader(compressedGuestAgent)
104+
if err != nil {
105+
return err
106+
}
107+
defer guestAgent.Close()
108+
_, err = io.Copy(tmpGuestAgent, guestAgent)
109+
if err != nil {
110+
return err
111+
}
112+
tmpGuestAgent.Close()
113+
guestAgentBinary = tmpGuestAgent.Name()
114+
defer os.RemoveAll(guestAgentBinary)
115+
}
116+
tmpname := "lima-guestagent"
117+
tmp, err := shell(ctx, sshExe, sshFlags, hostname, "mktemp", "-t", "lima-guestagent.XXXXXX")
118+
if err != nil {
119+
return err
120+
}
121+
bin := prefix + "/bin/lima-guestagent"
122+
logrus.Infof("Copying %q to %s:%s", guestAgentFilename, inst.Name, tmpname)
123+
scpArgs := []string{guestAgentBinary, hostname + ":" + tmp}
124+
if err := runCmd(ctx, scpExe, scpFlags, scpArgs...); err != nil {
125+
return nil
126+
}
127+
logrus.Infof("Installing %s to %s", tmpname, bin)
128+
sshArgs := []string{hostname, "sudo", "install", "-m", "755", tmp, bin}
129+
if err := runCmd(ctx, sshExe, sshFlags, sshArgs...); err != nil {
130+
return nil
131+
}
132+
_, _ = shell(ctx, sshExe, sshFlags, hostname, "rm", tmp)
133+
134+
// nerdctl-full.tgz
135+
nerdctlFilename := cacheutil.NerdctlArchive(inst.Config)
136+
if nerdctlFilename != "" {
137+
nerdctlArchive, err := cacheutil.EnsureNerdctlArchiveCache(cmd.Context(), inst.Config, false)
138+
if err != nil {
139+
return err
140+
}
141+
tmpname := "nerdctl-full.tgz"
142+
tmp, err := shell(ctx, sshExe, sshFlags, hostname, "mktemp", "-t", "nerdctl-full.XXXXXX.tgz")
143+
if err != nil {
144+
return err
145+
}
146+
logrus.Infof("Copying %q to %s:%s", nerdctlFilename, inst.Name, tmpname)
147+
scpArgs := []string{nerdctlArchive, hostname + ":" + tmp}
148+
if err := runCmd(ctx, scpExe, scpFlags, scpArgs...); err != nil {
149+
return nil
150+
}
151+
logrus.Infof("Installing %s in %s", tmpname, prefix)
152+
sshArgs := []string{hostname, "sudo", "tar", "Cxzf", prefix, tmp}
153+
if err := runCmd(ctx, sshExe, sshFlags, sshArgs...); err != nil {
154+
return nil
155+
}
156+
_, _ = shell(ctx, sshExe, sshFlags, hostname, "rm", tmp)
157+
}
158+
159+
return nil
160+
}

cmd/limactl/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func newApp() *cobra.Command {
175175
newValidateCommand(),
176176
newPruneCommand(),
177177
newHostagentCommand(),
178+
newGuestInstallCommand(),
178179
newInfoCommand(),
179180
newShowSSHCommand(),
180181
newDebugCommand(),

pkg/cacheutil/cacheutil.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cacheutil
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"path"
10+
11+
"github.com/lima-vm/lima/v2/pkg/downloader"
12+
"github.com/lima-vm/lima/v2/pkg/fileutils"
13+
"github.com/lima-vm/lima/v2/pkg/limatype"
14+
)
15+
16+
// NerdctlArchive returns the basename of the archive.
17+
func NerdctlArchive(y *limatype.LimaYAML) string {
18+
if *y.Containerd.System || *y.Containerd.User {
19+
for _, f := range y.Containerd.Archives {
20+
if f.Arch == *y.Arch {
21+
return path.Base(f.Location)
22+
}
23+
}
24+
}
25+
return ""
26+
}
27+
28+
// EnsureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive
29+
// into the cache before launching the hostagent process, so that we can show the progress in tty.
30+
// https://github.com/lima-vm/lima/issues/326
31+
func EnsureNerdctlArchiveCache(ctx context.Context, y *limatype.LimaYAML, created bool) (string, error) {
32+
if !*y.Containerd.System && !*y.Containerd.User {
33+
// nerdctl archive is not needed
34+
return "", nil
35+
}
36+
37+
errs := make([]error, len(y.Containerd.Archives))
38+
for i, f := range y.Containerd.Archives {
39+
// Skip downloading again if the file is already in the cache
40+
if created && f.Arch == *y.Arch && !downloader.IsLocal(f.Location) {
41+
path, err := fileutils.CachedFile(f)
42+
if err == nil {
43+
return path, nil
44+
}
45+
}
46+
path, err := fileutils.DownloadFile(ctx, "", f, false, "the nerdctl archive", *y.Arch)
47+
if err != nil {
48+
errs[i] = err
49+
continue
50+
}
51+
if path == "" {
52+
if downloader.IsLocal(f.Location) {
53+
return f.Location, nil
54+
}
55+
return "", fmt.Errorf("cache did not contain %q", f.Location)
56+
}
57+
return path, nil
58+
}
59+
60+
return "", fileutils.Errors(errs)
61+
}

pkg/instance/start.go

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/lima-vm/go-qcow2reader"
2020
"github.com/sirupsen/logrus"
2121

22-
"github.com/lima-vm/lima/v2/pkg/downloader"
22+
"github.com/lima-vm/lima/v2/pkg/cacheutil"
2323
"github.com/lima-vm/lima/v2/pkg/driver"
2424
"github.com/lima-vm/lima/v2/pkg/driverutil"
2525
"github.com/lima-vm/lima/v2/pkg/executil"
@@ -37,41 +37,6 @@ import (
3737
// to be running before timing out.
3838
const DefaultWatchHostAgentEventsTimeout = 10 * time.Minute
3939

40-
// ensureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive
41-
// into the cache before launching the hostagent process, so that we can show the progress in tty.
42-
// https://github.com/lima-vm/lima/issues/326
43-
func ensureNerdctlArchiveCache(ctx context.Context, y *limatype.LimaYAML, created bool) (string, error) {
44-
if !*y.Containerd.System && !*y.Containerd.User {
45-
// nerdctl archive is not needed
46-
return "", nil
47-
}
48-
49-
errs := make([]error, len(y.Containerd.Archives))
50-
for i, f := range y.Containerd.Archives {
51-
// Skip downloading again if the file is already in the cache
52-
if created && f.Arch == *y.Arch && !downloader.IsLocal(f.Location) {
53-
path, err := fileutils.CachedFile(f)
54-
if err == nil {
55-
return path, nil
56-
}
57-
}
58-
path, err := fileutils.DownloadFile(ctx, "", f, false, "the nerdctl archive", *y.Arch)
59-
if err != nil {
60-
errs[i] = err
61-
continue
62-
}
63-
if path == "" {
64-
if downloader.IsLocal(f.Location) {
65-
return f.Location, nil
66-
}
67-
return "", fmt.Errorf("cache did not contain %q", f.Location)
68-
}
69-
return path, nil
70-
}
71-
72-
return "", fileutils.Errors(errs)
73-
}
74-
7540
type Prepared struct {
7641
Driver driver.Driver
7742
GuestAgent string
@@ -154,7 +119,7 @@ func Prepare(ctx context.Context, inst *limatype.Instance) (*Prepared, error) {
154119
return nil, err
155120
}
156121

157-
nerdctlArchiveCache, err := ensureNerdctlArchiveCache(ctx, inst.Config, created)
122+
nerdctlArchiveCache, err := cacheutil.EnsureNerdctlArchiveCache(ctx, inst.Config, created)
158123
if err != nil {
159124
return nil, err
160125
}

0 commit comments

Comments
 (0)