From 431cbffab68ddf8a8d418314fe99fe9290cd1edb Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Thu, 18 Apr 2024 09:09:14 -0400 Subject: [PATCH] machine: Add provider detection API Extends the `pkg/machine/provider` package to add an API which includes provider detection based on the host operating system. Signed-off-by: Jake Correnti --- pkg/machine/provider/platform.go | 30 ++++++++++ pkg/machine/provider/platform_darwin.go | 47 +++++++++++++++ pkg/machine/provider/platform_test.go | 76 ++++++++++++++++++++++++ pkg/machine/provider/platform_windows.go | 37 ++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 pkg/machine/provider/platform_test.go diff --git a/pkg/machine/provider/platform.go b/pkg/machine/provider/platform.go index c280df9634..b9cca9ca7a 100644 --- a/pkg/machine/provider/platform.go +++ b/pkg/machine/provider/platform.go @@ -3,7 +3,9 @@ package provider import ( + "errors" "fmt" + "io/fs" "os" "github.com/containers/common/pkg/config" @@ -35,3 +37,31 @@ func Get() (vmconfigs.VMProvider, error) { return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String()) } } + +// SupportedProviders returns the providers that are supported on the host operating system +func SupportedProviders() []define.VMType { + return []define.VMType{define.QemuVirt} +} + +// InstalledProviders returns the supported providers that are installed on the host +func InstalledProviders() ([]define.VMType, error) { + cfg, err := config.Default() + if err != nil { + return nil, err + } + _, err = cfg.FindHelperBinary(qemu.QemuCommand, true) + if errors.Is(err, fs.ErrNotExist) { + return []define.VMType{}, nil + } + if err != nil { + return nil, err + } + + return []define.VMType{define.QemuVirt}, nil +} + +// HasPermsForProvider returns whether the host operating system has the proper permissions to use the given provider +func HasPermsForProvider(provider define.VMType) bool { + // there are no permissions required for QEMU + return provider == define.QemuVirt +} diff --git a/pkg/machine/provider/platform_darwin.go b/pkg/machine/provider/platform_darwin.go index f3d100c379..51e697f39a 100644 --- a/pkg/machine/provider/platform_darwin.go +++ b/pkg/machine/provider/platform_darwin.go @@ -1,8 +1,13 @@ package provider import ( + "bytes" + "errors" "fmt" "os" + "os/exec" + "strconv" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/podman/v5/pkg/machine/applehv" @@ -36,3 +41,45 @@ func Get() (vmconfigs.VMProvider, error) { return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String()) } } + +// SupportedProviders returns the providers that are supported on the host operating system +func SupportedProviders() []define.VMType { + return []define.VMType{define.AppleHvVirt} +} + +// InstalledProviders returns the supported providers that are installed on the host +func InstalledProviders() ([]define.VMType, error) { + var outBuf bytes.Buffer + // Apple's Virtualization.Framework is only supported on MacOS 11.0+ + const SupportedMacOSVersion = 11 + + cmd := exec.Command("sw_vers", "--productVersion") + cmd.Stdout = &outBuf + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("unable to check current macOS version using `sw_vers --productVersion`: %s", err) + } + + // the output will be in the format of MAJOR.MINOR.PATCH + output := outBuf.String() + idx := strings.Index(output, ".") + if idx < 0 { + return nil, errors.New("invalid output provided by sw_vers --productVersion") + } + majorString := output[:idx] + majorInt, err := strconv.Atoi(majorString) + if err != nil { + return nil, err + } + + if majorInt >= SupportedMacOSVersion { + return []define.VMType{define.AppleHvVirt}, nil + } + + return []define.VMType{}, nil +} + +// HasPermsForProvider returns whether the host operating system has the proper permissions to use the given provider +func HasPermsForProvider(provider define.VMType) bool { + // there are no permissions required for AppleHV + return provider == define.AppleHvVirt +} diff --git a/pkg/machine/provider/platform_test.go b/pkg/machine/provider/platform_test.go new file mode 100644 index 0000000000..36621a2446 --- /dev/null +++ b/pkg/machine/provider/platform_test.go @@ -0,0 +1,76 @@ +package provider + +import ( + "runtime" + "testing" + + "github.com/containers/podman/v5/pkg/machine/define" + "github.com/stretchr/testify/assert" +) + +func TestSupportedProviders(t *testing.T) { + switch runtime.GOOS { + case "darwin": + assert.Equal(t, []define.VMType{define.AppleHvVirt}, SupportedProviders()) + case "windows": + assert.Equal(t, []define.VMType{define.WSLVirt, define.HyperVVirt}, SupportedProviders()) + case "linux": + assert.Equal(t, []define.VMType{define.QemuVirt}, SupportedProviders()) + } +} + +func TestInstalledProviders(t *testing.T) { + installed, err := InstalledProviders() + assert.Nil(t, err) + switch runtime.GOOS { + case "darwin": + assert.Equal(t, []define.VMType{define.AppleHvVirt}, installed) + case "windows": + provider, err := Get() + assert.Nil(t, err) + assert.Contains(t, installed, provider) + case "linux": + assert.Equal(t, []define.VMType{define.QemuVirt}, installed) + } +} + +func TestHasPermsForProvider(t *testing.T) { + provider, err := Get() + assert.Nil(t, err) + assert.True(t, HasPermsForProvider(provider.VMType())) +} + +func TestHasBadPerms(t *testing.T) { + switch runtime.GOOS { + case "darwin": + assert.False(t, HasPermsForProvider(define.QemuVirt)) + case "windows": + assert.False(t, HasPermsForProvider(define.QemuVirt)) + case "linux": + assert.False(t, HasPermsForProvider(define.AppleHvVirt)) + } +} + +func TestBadSupportedProviders(t *testing.T) { + switch runtime.GOOS { + case "darwin": + assert.NotEqual(t, []define.VMType{define.QemuVirt}, SupportedProviders()) + case "windows": + assert.NotEqual(t, []define.VMType{define.QemuVirt}, SupportedProviders()) + case "linux": + assert.NotEqual(t, []define.VMType{define.AppleHvVirt}, SupportedProviders()) + } +} + +func TestBadInstalledProviders(t *testing.T) { + installed, err := InstalledProviders() + assert.Nil(t, err) + switch runtime.GOOS { + case "darwin": + assert.NotEqual(t, []define.VMType{define.QemuVirt}, installed) + case "windows": + assert.NotContains(t, installed, define.QemuVirt) + case "linux": + assert.NotEqual(t, []define.VMType{define.AppleHvVirt}, installed) + } +} diff --git a/pkg/machine/provider/platform_windows.go b/pkg/machine/provider/platform_windows.go index 4287f1c5f4..f6a4577b24 100644 --- a/pkg/machine/provider/platform_windows.go +++ b/pkg/machine/provider/platform_windows.go @@ -4,8 +4,10 @@ import ( "fmt" "os" + "github.com/containers/libhvee/pkg/hypervctl" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/containers/podman/v5/pkg/machine/wsl" + "github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/common/pkg/config" "github.com/containers/podman/v5/pkg/machine/define" @@ -40,3 +42,38 @@ func Get() (vmconfigs.VMProvider, error) { return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String()) } } + +// SupportedProviders returns the providers that are supported on the host operating system +func SupportedProviders() []define.VMType { + return []define.VMType{define.HyperVVirt, define.WSLVirt} +} + +// InstalledProviders returns the supported providers that are installed on the host +func InstalledProviders() ([]define.VMType, error) { + installed := []define.VMType{} + if wutil.IsWSLInstalled() { + installed = append(installed, define.WSLVirt) + } + + service, err := hypervctl.NewLocalHyperVService() + if err == nil { + installed = append(installed, define.HyperVVirt) + } + service.Close() + + return installed, nil +} + +// HasPermsForProvider returns whether the host operating system has the proper permissions to use the given provider +func HasPermsForProvider(provider define.VMType) bool { + switch provider { + case define.QemuVirt: + fallthrough + case define.AppleHvVirt: + return false + case define.HyperVVirt: + return wsl.HasAdminRights() + } + + return true +}