diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index 0d78bc1e0cd..008f9ec640d 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -21,6 +21,7 @@ import ( "github.com/lima-vm/lima/pkg/limatmpl" "github.com/lima-vm/lima/pkg/limayaml" networks "github.com/lima-vm/lima/pkg/networks/reconcile" + "github.com/lima-vm/lima/pkg/registry" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/templatestore" @@ -32,6 +33,7 @@ func registerCreateFlags(cmd *cobra.Command, commentPrefix string) { flags := cmd.Flags() flags.String("name", "", commentPrefix+"Override the instance name") flags.Bool("list-templates", false, commentPrefix+"List available templates and exit") + flags.Bool("list-drivers", false, commentPrefix+"List available drivers and exit") editflags.RegisterCreate(cmd, commentPrefix) } @@ -393,6 +395,14 @@ func createStartActionCommon(cmd *cobra.Command, _ []string) (exit bool, err err _, _ = fmt.Fprintln(w, f.Name) } return true, nil + } else if listDrivers, err := cmd.Flags().GetBool("list-drivers"); err != nil { + return true, err + } else if listDrivers { + w := cmd.OutOrStdout() + for k := range registry.List() { + _, _ = fmt.Fprintln(w, k) + } + return true, nil } return false, nil } diff --git a/pkg/driverutil/driverutil.go b/pkg/driverutil/driverutil.go deleted file mode 100644 index 1a95721105a..00000000000 --- a/pkg/driverutil/driverutil.go +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package driverutil - -import ( - "github.com/lima-vm/lima/pkg/driver/vz" - "github.com/lima-vm/lima/pkg/driver/wsl2" - "github.com/lima-vm/lima/pkg/limayaml" -) - -// Drivers returns the available drivers. -func Drivers() []string { - drivers := []string{limayaml.QEMU} - if vz.Enabled { - drivers = append(drivers, limayaml.VZ) - } - if wsl2.Enabled { - drivers = append(drivers, limayaml.WSL2) - } - return drivers -} diff --git a/pkg/limainfo/limainfo.go b/pkg/limainfo/limainfo.go index 5311cca2805..bd21e8d1210 100644 --- a/pkg/limainfo/limainfo.go +++ b/pkg/limainfo/limainfo.go @@ -9,8 +9,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/limayaml" + "github.com/lima-vm/lima/pkg/registry" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/templatestore" "github.com/lima-vm/lima/pkg/usrlocalsharelima" @@ -23,9 +23,14 @@ type LimaInfo struct { DefaultTemplate *limayaml.LimaYAML `json:"defaultTemplate"` LimaHome string `json:"limaHome"` VMTypes []string `json:"vmTypes"` // since Lima v0.14.2 + VMTypesEx map[string]DriverExt `json:"vmTypesEx"` // since Lima v2.0.0 GuestAgents map[limayaml.Arch]GuestAgent `json:"guestAgents"` // since Lima v1.1.0 } +type DriverExt struct { + Location string `json:"location,omitempty"` // since Lima v2.0.0 +} + type GuestAgent struct { Location string `json:"location"` // since Lima v1.1.0 } @@ -42,10 +47,25 @@ func New() (*LimaInfo, error) { if err != nil { return nil, err } + + reg := registry.List() + if len(reg) == 0 { + return nil, errors.New("no VM types found; ensure that the drivers are properly registered") + } + vmTypesEx := make(map[string]DriverExt) + var vmTypes []string + for name, path := range reg { + vmTypesEx[name] = DriverExt{ + Location: path, + } + vmTypes = append(vmTypes, name) + } + info := &LimaInfo{ Version: version.Version, DefaultTemplate: y, - VMTypes: driverutil.Drivers(), + VMTypes: vmTypes, + VMTypesEx: vmTypesEx, GuestAgents: make(map[limayaml.Arch]GuestAgent), } info.Templates, err = templatestore.Templates() diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 00000000000..4b269aeeaa1 --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package registry + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/pkg/driver" + "github.com/lima-vm/lima/pkg/driver/vz" + "github.com/lima-vm/lima/pkg/driver/wsl2" + "github.com/lima-vm/lima/pkg/limayaml" + "github.com/lima-vm/lima/pkg/usrlocalsharelima" +) + +const ( + Internal = "internal" +) + +type ExternalDriver struct { + Name string + InstanceName string + Command *exec.Cmd + SocketPath string + Path string + Ctx context.Context + Logger *logrus.Logger + CancelFunc context.CancelFunc +} + +var ( + internalDrivers = make(map[string]driver.Driver) + ExternalDrivers = make(map[string]*ExternalDriver) +) + +func List() map[string]string { + if err := discoverDrivers(); err != nil { + logrus.Warnf("Error discovering drivers: %v", err) + } + + vmTypes := make(map[string]string) + for name := range internalDrivers { + vmTypes[name] = Internal + } + for name, d := range ExternalDrivers { + vmTypes[name] = d.Path + } + + // This block will be removed while merging the internal driver pull request(#3693). + if len(vmTypes) == 0 { + vmTypes[limayaml.QEMU] = Internal + if vz.Enabled { + vmTypes[limayaml.VZ] = Internal + } + if wsl2.Enabled { + vmTypes[limayaml.WSL2] = Internal + } + } + + return vmTypes +} + +func Get(name string) (*ExternalDriver, driver.Driver, bool) { + if err := discoverDrivers(); err != nil { + logrus.Warnf("Error discovering drivers: %v", err) + } + + internalDriver, exists := internalDrivers[name] + if !exists { + externalDriver, exists := ExternalDrivers[name] + if exists { + return externalDriver, nil, exists + } + } + return nil, internalDriver, exists +} + +func registerExternalDriver(name, path string) { + if _, exists := ExternalDrivers[name]; exists { + return + } + + if _, exists := internalDrivers[name]; exists { + logrus.Debugf("Driver %q is already registered as an internal driver, skipping external registration", name) + return + } + + ExternalDrivers[name] = &ExternalDriver{ + Name: name, + Path: path, + Logger: logrus.New(), + } +} + +func discoverDrivers() error { + prefix, err := usrlocalsharelima.Prefix() + if err != nil { + return err + } + stdDriverDir := filepath.Join(prefix, "libexec", "lima") + + logrus.Debugf("Discovering external drivers in %s", stdDriverDir) + if _, err := os.Stat(stdDriverDir); err == nil { + if err := discoverDriversInDir(stdDriverDir); err != nil { + logrus.Warnf("Error discovering external drivers in %q: %v", stdDriverDir, err) + } + } + + if driverPaths := os.Getenv("LIMA_DRIVERS_PATH"); driverPaths != "" { + paths := filepath.SplitList(driverPaths) + for _, path := range paths { + if path == "" { + continue + } + + info, err := os.Stat(path) + if err != nil { + logrus.Warnf("Error accessing external driver path %q: %v", path, err) + continue + } + + if info.IsDir() { + if err := discoverDriversInDir(path); err != nil { + logrus.Warnf("Error discovering external drivers in %q: %v", path, err) + } + } else if isExecutable(info.Mode()) { + registerDriverFile(path) + } + } + } + + return nil +} + +func discoverDriversInDir(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read driver directory %q: %w", dir, err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + info, err := entry.Info() + if err != nil { + logrus.Warnf("Failed to get info for %q: %v", entry.Name(), err) + continue + } + + if !isExecutable(info.Mode()) { + continue + } + + driverPath := filepath.Join(dir, entry.Name()) + registerDriverFile(driverPath) + } + + return nil +} + +func registerDriverFile(path string) { + base := filepath.Base(path) + if !strings.HasPrefix(base, "lima-driver-") { + return + } + + name := strings.TrimPrefix(base, "lima-driver-") + + registerExternalDriver(name, path) +} + +func isExecutable(mode os.FileMode) bool { + return mode&0o111 != 0 +} diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go index 0f08e609486..b218fb31095 100644 --- a/pkg/usrlocalsharelima/usrlocalsharelima.go +++ b/pkg/usrlocalsharelima/usrlocalsharelima.go @@ -165,3 +165,12 @@ func chooseGABinary(candidates []string) (string, error) { return entries[0], nil } } + +// Prefix returns the directory, which is two levels above the lima share directory. +func Prefix() (string, error) { + dir, err := Dir() + if err != nil { + return "", err + } + return filepath.Dir(filepath.Dir(dir)), nil +}