Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hidden command to install the guest components #2028

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions cmd/limactl/guest-install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package main

import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"

"github.com/lima-vm/lima/pkg/cacheutil"
"github.com/lima-vm/lima/pkg/store"
"github.com/lima-vm/lima/pkg/store/filenames"
"github.com/lima-vm/lima/pkg/usrlocalsharelima"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func newGuestInstallCommand() *cobra.Command {
guestInstallCommand := &cobra.Command{
Use: "guest-install INSTANCE",
Short: "Install guest components",
Args: WrapArgsError(cobra.MaximumNArgs(1)),
RunE: guestInstallAction,
ValidArgsFunction: cobra.NoFileCompletions,
Hidden: true,
}
return guestInstallCommand
}

func runCmd(name string, flags []string, args ...string) error {
cmd := exec.Command(name, append(flags, args...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
logrus.Debugf("executing %v", cmd.Args)
return cmd.Run()
}

func shell(name string, flags []string, args ...string) (string, error) {
cmd := exec.Command(name, append(flags, args...)...)
out, err := cmd.Output()
if err != nil {
return "", err
}
out = bytes.TrimSuffix(out, []byte{'\n'})
return string(out), nil
}

func guestInstallAction(cmd *cobra.Command, args []string) error {
instName := DefaultInstanceName
if len(args) > 0 {
instName = args[0]
}

inst, err := store.Inspect(instName)
if err != nil {
return err
}
if inst.Status == store.StatusStopped {
return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName)
}

sshExe := "ssh"
sshConfig := filepath.Join(inst.Dir, filenames.SSHConfig)
sshFlags := []string{"-F", sshConfig}

scpExe := "scp"
scpFlags := sshFlags

hostname := fmt.Sprintf("lima-%s", inst.Name)
prefix := *inst.Config.GuestInstallPrefix

// lima-guestagent
guestAgentBinary, err := usrlocalsharelima.GuestAgentBinary(*inst.Config.OS, *inst.Config.Arch)
if err != nil {
return err
}
guestAgentFilename := filepath.Base(guestAgentBinary)
if _, err := os.Stat(guestAgentBinary); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
compressedGuestAgent, err := os.Open(guestAgentBinary + ".gz")
if err != nil {
return err
}
defer compressedGuestAgent.Close()
tmpGuestAgent, err := os.CreateTemp("", "lima-guestagent-")
if err != nil {
return err
}
logrus.Debugf("Decompressing %s.gz", guestAgentBinary)
guestAgent, err := gzip.NewReader(compressedGuestAgent)
if err != nil {
return err
}
defer guestAgent.Close()
_, err = io.Copy(tmpGuestAgent, guestAgent)
if err != nil {
return err
}
tmpGuestAgent.Close()
guestAgentBinary = tmpGuestAgent.Name()
defer os.RemoveAll(guestAgentBinary)
}
tmpname := "lima-guestagent"
tmp, err := shell(sshExe, sshFlags, hostname, "mktemp", "-t", "lima-guestagent.XXXXXX")
if err != nil {
return err
}
bin := prefix + "/bin/lima-guestagent"
logrus.Infof("Copying %q to %s:%s", guestAgentFilename, inst.Name, tmpname)
scpArgs := []string{guestAgentBinary, hostname + ":" + tmp}
if err := runCmd(scpExe, scpFlags, scpArgs...); err != nil {
return nil
}
logrus.Infof("Installing %s to %s", tmpname, bin)
sshArgs := []string{hostname, "sudo", "install", "-m", "755", tmp, bin}
if err := runCmd(sshExe, sshFlags, sshArgs...); err != nil {
return nil
}
_, _ = shell(sshExe, sshFlags, hostname, "rm", tmp)

// nerdctl-full.tgz
nerdctlFilename := cacheutil.NerdctlArchive(inst.Config)
if nerdctlFilename != "" {
nerdctlArchive, err := cacheutil.EnsureNerdctlArchiveCache(cmd.Context(), inst.Config, false)
if err != nil {
return err
}
tmpname := "nerdctl-full.tgz"
tmp, err := shell(sshExe, sshFlags, hostname, "mktemp", "-t", "nerdctl-full.XXXXXX.tgz")
if err != nil {
return err
}
logrus.Infof("Copying %q to %s:%s", nerdctlFilename, inst.Name, tmpname)
scpArgs := []string{nerdctlArchive, hostname + ":" + tmp}
if err := runCmd(scpExe, scpFlags, scpArgs...); err != nil {
return nil
}
logrus.Infof("Installing %s in %s", tmpname, prefix)
sshArgs := []string{hostname, "sudo", "tar", "Cxzf", prefix, tmp}
if err := runCmd(sshExe, sshFlags, sshArgs...); err != nil {
return nil
}
_, _ = shell(sshExe, sshFlags, hostname, "rm", tmp)
}

return nil
}
1 change: 1 addition & 0 deletions cmd/limactl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func newApp() *cobra.Command {
newSudoersCommand(),
newPruneCommand(),
newHostagentCommand(),
newGuestInstallCommand(),
newInfoCommand(),
newShowSSHCommand(),
newDebugCommand(),
Expand Down
58 changes: 58 additions & 0 deletions pkg/cacheutil/cacheutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cacheutil

import (
"context"
"fmt"
"path"

"github.com/lima-vm/lima/pkg/downloader"
"github.com/lima-vm/lima/pkg/fileutils"
"github.com/lima-vm/lima/pkg/limayaml"
)

// NerdctlArchive returns the basename of the archive.
func NerdctlArchive(y *limayaml.LimaYAML) string {
if *y.Containerd.System || *y.Containerd.User {
for _, f := range y.Containerd.Archives {
if f.Arch == *y.Arch {
return path.Base(f.Location)
}
}
}
return ""
}

// EnsureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive
// into the cache before launching the hostagent process, so that we can show the progress in tty.
// https://github.com/lima-vm/lima/issues/326
func EnsureNerdctlArchiveCache(ctx context.Context, y *limayaml.LimaYAML, created bool) (string, error) {
if !*y.Containerd.System && !*y.Containerd.User {
// nerdctl archive is not needed
return "", nil
}

errs := make([]error, len(y.Containerd.Archives))
for i, f := range y.Containerd.Archives {
// Skip downloading again if the file is already in the cache
if created && f.Arch == *y.Arch && !downloader.IsLocal(f.Location) {
path, err := fileutils.CachedFile(f)
if err == nil {
return path, nil
}
}
path, err := fileutils.DownloadFile(ctx, "", f, false, "the nerdctl archive", *y.Arch)
if err != nil {
errs[i] = err
continue
}
if path == "" {
if downloader.IsLocal(f.Location) {
return f.Location, nil
}
return "", fmt.Errorf("cache did not contain %q", f.Location)
}
return path, nil
}

return "", fileutils.Errors(errs)
}
40 changes: 2 additions & 38 deletions pkg/instance/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/coreos/go-semver/semver"
"github.com/lima-vm/lima/pkg/cacheutil"
"github.com/lima-vm/lima/pkg/driver"
"github.com/lima-vm/lima/pkg/driverutil"
"github.com/lima-vm/lima/pkg/executil"
Expand All @@ -23,8 +24,6 @@ import (
"github.com/lima-vm/lima/pkg/qemu/entitlementutil"
"github.com/mattn/go-isatty"

"github.com/lima-vm/lima/pkg/downloader"
"github.com/lima-vm/lima/pkg/fileutils"
hostagentevents "github.com/lima-vm/lima/pkg/hostagent/events"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/store"
Expand All @@ -36,41 +35,6 @@ import (
// to be running before timing out.
const DefaultWatchHostAgentEventsTimeout = 10 * time.Minute

// ensureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive
// into the cache before launching the hostagent process, so that we can show the progress in tty.
// https://github.com/lima-vm/lima/issues/326
func ensureNerdctlArchiveCache(ctx context.Context, y *limayaml.LimaYAML, created bool) (string, error) {
if !*y.Containerd.System && !*y.Containerd.User {
// nerdctl archive is not needed
return "", nil
}

errs := make([]error, len(y.Containerd.Archives))
for i, f := range y.Containerd.Archives {
// Skip downloading again if the file is already in the cache
if created && f.Arch == *y.Arch && !downloader.IsLocal(f.Location) {
path, err := fileutils.CachedFile(f)
if err == nil {
return path, nil
}
}
path, err := fileutils.DownloadFile(ctx, "", f, false, "the nerdctl archive", *y.Arch)
if err != nil {
errs[i] = err
continue
}
if path == "" {
if downloader.IsLocal(f.Location) {
return f.Location, nil
}
return "", fmt.Errorf("cache did not contain %q", f.Location)
}
return path, nil
}

return "", fileutils.Errors(errs)
}

type Prepared struct {
Driver driver.Driver
NerdctlArchiveCache string
Expand Down Expand Up @@ -98,7 +62,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
if err := limaDriver.CreateDisk(ctx); err != nil {
return nil, err
}
nerdctlArchiveCache, err := ensureNerdctlArchiveCache(ctx, inst.Config, created)
nerdctlArchiveCache, err := cacheutil.EnsureNerdctlArchiveCache(ctx, inst.Config, created)
if err != nil {
return nil, err
}
Expand Down
Loading