From 41f945fc249b2a40220b312a2bbc43cc45f5c24b Mon Sep 17 00:00:00 2001 From: Ashley Cui Date: Wed, 21 Aug 2024 09:30:46 -0400 Subject: [PATCH] machine: Add -all-providers flag to machine list Podman machine list now supports a new option, --all-providers, which lists all machines from all providers. Signed-off-by: Ashley Cui --- cmd/podman/machine/list.go | 60 ++++++++++----- cmd/podman/machine/reset.go | 11 ++- .../markdown/podman-machine-list.1.md.in | 4 + pkg/machine/e2e/config_darwin_test.go | 11 +++ pkg/machine/e2e/config_linux_test.go | 4 + pkg/machine/e2e/config_list_test.go | 16 +++- pkg/machine/e2e/config_windows_test.go | 11 +++ pkg/machine/e2e/list_test.go | 43 +++++++++++ pkg/machine/provider/platform.go | 73 +++--------------- pkg/machine/provider/platform_darwin.go | 40 +++++----- pkg/machine/provider/platform_unix.go | 77 +++++++++++++++++++ pkg/machine/provider/platform_windows.go | 39 +++++----- 12 files changed, 261 insertions(+), 128 deletions(-) create mode 100644 pkg/machine/provider/platform_unix.go diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 04ec6eec22..bddcf49c2a 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -7,15 +7,18 @@ import ( "os" "sort" "strconv" + "strings" "time" "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/report" "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/registry" "github.com/containers/podman/v5/cmd/podman/validate" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/machine" + provider2 "github.com/containers/podman/v5/pkg/machine/provider" "github.com/containers/podman/v5/pkg/machine/shim" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/docker/go-units" @@ -24,11 +27,12 @@ import ( var ( lsCmd = &cobra.Command{ - Use: "list [options]", - Aliases: []string{"ls"}, - Short: "List machines", - Long: "List managed virtual machines.", - PersistentPreRunE: machinePreRunE, + Use: "list [options]", + Aliases: []string{"ls"}, + Short: "List machines", + Long: "List managed virtual machines.", + // do not use machinePreRunE, as that pre-sets the provider + PersistentPreRunE: rootlessOnly, RunE: list, Args: validate.NoArgs, ValidArgsFunction: completion.AutocompleteNone, @@ -40,9 +44,10 @@ var ( ) type listFlagType struct { - format string - noHeading bool - quiet bool + format string + noHeading bool + quiet bool + allProviders bool } func init() { @@ -57,6 +62,7 @@ func init() { _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ListReporter{})) flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print headers") flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Show only machine names") + flags.BoolVar(&listFlag.allProviders, "all-providers", false, "Show machines from all providers") } func list(cmd *cobra.Command, args []string) error { @@ -64,8 +70,18 @@ func list(cmd *cobra.Command, args []string) error { opts machine.ListOptions err error ) + var providers []vmconfigs.VMProvider + if listFlag.allProviders { + providers = provider2.GetAll() + } else { + provider, err = provider2.Get() + if err != nil { + return err + } + providers = []vmconfigs.VMProvider{provider} + } - listResponse, err := shim.List([]vmconfigs.VMProvider{provider}, opts) + listResponse, err := shim.List(providers, opts) if err != nil { return err } @@ -79,12 +95,8 @@ func list(cmd *cobra.Command, args []string) error { return listResponse[i].Running }) - defaultCon := "" - con, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true) - if err == nil { - // ignore the error here we only want to know if we have a default connection to show it in list - defaultCon = con.Name - } + // ignore the error here we only want to know if we have a default connection to show it in list + defaultCon, _ := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true) if report.IsJSON(listFlag.format) { machineReporter := toMachineFormat(listResponse, defaultCon) @@ -152,11 +164,16 @@ func streamName(imageStream string) string { return imageStream } -func toMachineFormat(vms []*machine.ListResponse, defaultCon string) []*entities.ListReporter { +func toMachineFormat(vms []*machine.ListResponse, defaultCon *config.Connection) []*entities.ListReporter { machineResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { + isDefault := false + // check port, in case we somehow have machines with the same name in different providers + if defaultCon != nil { + isDefault = vm.Name == defaultCon.Name && strings.Contains(defaultCon.URI, strconv.Itoa((vm.Port))) + } response := new(entities.ListReporter) - response.Default = vm.Name == defaultCon + response.Default = isDefault response.Name = vm.Name response.Running = vm.Running response.LastUp = strTime(vm.LastUp) @@ -177,11 +194,16 @@ func toMachineFormat(vms []*machine.ListResponse, defaultCon string) []*entities return machineResponses } -func toHumanFormat(vms []*machine.ListResponse, defaultCon string) []*entities.ListReporter { +func toHumanFormat(vms []*machine.ListResponse, defaultCon *config.Connection) []*entities.ListReporter { humanResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { response := new(entities.ListReporter) - if vm.Name == defaultCon { + isDefault := false + // check port, in case we somehow have machines with the same name in different providers + if defaultCon != nil { + isDefault = vm.Name == defaultCon.Name && strings.Contains(defaultCon.URI, strconv.Itoa((vm.Port))) + } + if isDefault { response.Name = vm.Name + "*" response.Default = true } else { diff --git a/cmd/podman/machine/reset.go b/cmd/podman/machine/reset.go index 9b76f97a86..2d49d8f8cd 100644 --- a/cmd/podman/machine/reset.go +++ b/cmd/podman/machine/reset.go @@ -15,6 +15,7 @@ import ( "github.com/containers/podman/v5/pkg/machine" provider2 "github.com/containers/podman/v5/pkg/machine/provider" "github.com/containers/podman/v5/pkg/machine/shim" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -50,10 +51,18 @@ func reset(_ *cobra.Command, _ []string) error { err error ) - providers, err := provider2.GetAll(resetOptions.Force) + providers := provider2.GetAll() if err != nil { return err } + for _, p := range providers { + hasPerms := provider2.HasPermsForProvider(p.VMType()) + isInstalled, err := provider2.IsInstalled(p.VMType()) + if !hasPerms && (isInstalled || err != nil) && !resetOptions.Force { + logrus.Warnf("Managing %s machines require admin authority.", p.VMType().String()) + logrus.Warnf("Continuing to reset may cause Podman to be unaware of remaining VMs in the VM manager.") + } + } if !resetOptions.Force { listResponse, err := shim.List(providers, machine.ListOptions{}) diff --git a/docs/source/markdown/podman-machine-list.1.md.in b/docs/source/markdown/podman-machine-list.1.md.in index 6e5568c077..60ebb12294 100644 --- a/docs/source/markdown/podman-machine-list.1.md.in +++ b/docs/source/markdown/podman-machine-list.1.md.in @@ -26,6 +26,10 @@ environment variable while the machines are running can lead to unexpected behav ## OPTIONS +#### **--all-providers** + +Show machines from all providers + #### **--format**=*format* Change the default output format. This can be of a supported type like 'json' diff --git a/pkg/machine/e2e/config_darwin_test.go b/pkg/machine/e2e/config_darwin_test.go index a04695c32c..d7860a8814 100644 --- a/pkg/machine/e2e/config_darwin_test.go +++ b/pkg/machine/e2e/config_darwin_test.go @@ -1,3 +1,14 @@ package e2e_test +import "github.com/containers/podman/v5/pkg/machine/define" + const podmanBinary = "../../../bin/darwin/podman" + +func getOtherProvider() string { + if isVmtype(define.AppleHvVirt) { + return "libkrun" + } else if isVmtype(define.LibKrun) { + return "applehv" + } + return "" +} diff --git a/pkg/machine/e2e/config_linux_test.go b/pkg/machine/e2e/config_linux_test.go index f07121a90f..46107a10dd 100644 --- a/pkg/machine/e2e/config_linux_test.go +++ b/pkg/machine/e2e/config_linux_test.go @@ -1,3 +1,7 @@ package e2e_test const podmanBinary = "../../../bin/podman-remote" + +func getOtherProvider() string { + return "" +} diff --git a/pkg/machine/e2e/config_list_test.go b/pkg/machine/e2e/config_list_test.go index 78f9edc62f..509cc5a03f 100644 --- a/pkg/machine/e2e/config_list_test.go +++ b/pkg/machine/e2e/config_list_test.go @@ -7,9 +7,10 @@ type listMachine struct { -q, --quiet Show only machine names */ - format string - noHeading bool - quiet bool + format string + noHeading bool + quiet bool + allProviders bool cmd []string } @@ -25,6 +26,10 @@ func (i *listMachine) buildCmd(m *machineTestBuilder) []string { if i.quiet { cmd = append(cmd, "--quiet") } + if i.allProviders { + cmd = append(cmd, "--all-providers") + } + i.cmd = cmd return cmd } @@ -43,3 +48,8 @@ func (i *listMachine) withFormat(format string) *listMachine { i.format = format return i } + +func (i *listMachine) withAllProviders() *listMachine { + i.allProviders = true + return i +} diff --git a/pkg/machine/e2e/config_windows_test.go b/pkg/machine/e2e/config_windows_test.go index 898fd7d7ab..950e85fd50 100644 --- a/pkg/machine/e2e/config_windows_test.go +++ b/pkg/machine/e2e/config_windows_test.go @@ -4,6 +4,8 @@ import ( "fmt" "os/exec" "strings" + + "github.com/containers/podman/v5/pkg/machine/define" ) const podmanBinary = "../../../bin/windows/podman.exe" @@ -23,3 +25,12 @@ func pgrep(n string) (string, error) { } return strOut, nil } + +func getOtherProvider() string { + if isVmtype(define.WSLVirt) { + return "hyperv" + } else if isVmtype(define.HyperVVirt) { + return "wsl" + } + return "" +} diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go index 4b2d738869..d6f5042ae3 100644 --- a/pkg/machine/e2e/list_test.go +++ b/pkg/machine/e2e/list_test.go @@ -1,12 +1,14 @@ package e2e_test import ( + "os" "slices" "strconv" "strings" "time" "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/machine/define" jsoniter "github.com/json-iterator/go" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -182,6 +184,47 @@ var _ = Describe("podman machine list", func() { Expect(listSession).To(Exit(0)) Expect(listSession.outputToString()).To(Equal("2GiB 11GiB")) }) + It("list machine from all providers", func() { + skipIfVmtype(define.QemuVirt, "linux only has one provider") + + // create machine on other provider + currprovider := os.Getenv("CONTAINERS_MACHINE_PROVIDER") + os.Setenv("CONTAINERS_MACHINE_PROVIDER", getOtherProvider()) + defer os.Setenv("CONTAINERS_MACHINE_PROVIDER", currprovider) + + // this may take a long time - we're not pre-fetching this image + othermach := new(initMachine) + session, err := mb.setName("otherprovider").setCmd(othermach).run() + // make sure to remove machine from other provider later + defer func() { + os.Setenv("CONTAINERS_MACHINE_PROVIDER", getOtherProvider()) + defer os.Setenv("CONTAINERS_MACHINE_PROVIDER", currprovider) + rm := new(rmMachine) + removed, err := mb.setName("otherprovider").setCmd(rm.withForce()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(removed).To(Exit(0)) + }() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + // change back to current provider + os.Setenv("CONTAINERS_MACHINE_PROVIDER", currprovider) + name := randomString() + i := new(initMachine) + session, err = mb.setName(name).setCmd(i.withImage(mb.imagePath)).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + list := new(listMachine) + listSession, err := mb.setCmd(list.withAllProviders().withFormat("{{.Name}}")).run() + Expect(err).NotTo(HaveOccurred()) + Expect(listSession).To(Exit(0)) + listNames := listSession.outputToStringSlice() + stripAsterisk(listNames) + Expect(listNames).To(HaveLen(2)) + Expect(slices.Contains(listNames, "otherprovider")).To(BeTrue()) + Expect(slices.Contains(listNames, name)).To(BeTrue()) + }) }) func stripAsterisk(sl []string) { diff --git a/pkg/machine/provider/platform.go b/pkg/machine/provider/platform.go index 40cf79053a..af2f3436a7 100644 --- a/pkg/machine/provider/platform.go +++ b/pkg/machine/provider/platform.go @@ -1,71 +1,20 @@ -//go:build !windows && !darwin - package provider import ( - "errors" - "fmt" - "io/fs" - "os" - - "github.com/containers/common/pkg/config" "github.com/containers/podman/v5/pkg/machine/define" - "github.com/containers/podman/v5/pkg/machine/qemu" - "github.com/containers/podman/v5/pkg/machine/vmconfigs" - "github.com/sirupsen/logrus" ) -func Get() (vmconfigs.VMProvider, error) { - cfg, err := config.Default() - if err != nil { - return nil, err - } - provider := cfg.Machine.Provider - if providerOverride, found := os.LookupEnv("CONTAINERS_MACHINE_PROVIDER"); found { - provider = providerOverride - } - resolvedVMType, err := define.ParseVMType(provider, define.QemuVirt) - if err != nil { - return nil, err - } - - logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String()) - switch resolvedVMType { - case define.QemuVirt: - return qemu.NewStubber() - default: - return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String()) - } -} - -func GetAll(_ bool) ([]vmconfigs.VMProvider, error) { - return []vmconfigs.VMProvider{new(qemu.QEMUStubber)}, nil -} - -// 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 + installedTypes := []define.VMType{} + providers := GetAll() + for _, p := range providers { + installed, err := IsInstalled(p.VMType()) + if err != nil { + return nil, err + } + if installed { + installedTypes = append(installedTypes, p.VMType()) + } } - 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 + return installedTypes, nil } diff --git a/pkg/machine/provider/platform_darwin.go b/pkg/machine/provider/platform_darwin.go index 0c76421546..f4241d5b62 100644 --- a/pkg/machine/provider/platform_darwin.go +++ b/pkg/machine/provider/platform_darwin.go @@ -42,11 +42,11 @@ func Get() (vmconfigs.VMProvider, error) { } } -func GetAll(_ bool) ([]vmconfigs.VMProvider, error) { +func GetAll() []vmconfigs.VMProvider { return []vmconfigs.VMProvider{ new(applehv.AppleHVStubber), new(libkrun.LibKrunStubber), - }, nil + } } // SupportedProviders returns the providers that are supported on the host operating system @@ -58,27 +58,23 @@ func SupportedProviders() []define.VMType { return supported } -// InstalledProviders returns the supported providers that are installed on the host -func InstalledProviders() ([]define.VMType, error) { - installed := []define.VMType{} - - appleHvInstalled, err := appleHvInstalled() - if err != nil { - return nil, err - } - if appleHvInstalled { - installed = append(installed, define.AppleHvVirt) - } - - libKrunInstalled, err := libKrunInstalled() - if err != nil { - return nil, err - } - if libKrunInstalled { - installed = append(installed, define.LibKrun) +func IsInstalled(provider define.VMType) (bool, error) { + switch provider { + case define.AppleHvVirt: + ahv, err := appleHvInstalled() + if err != nil { + return false, err + } + return ahv, nil + case define.LibKrun: + lkr, err := libKrunInstalled() + if err != nil { + return false, err + } + return lkr, nil + default: + return false, nil } - - return installed, nil } func appleHvInstalled() (bool, error) { diff --git a/pkg/machine/provider/platform_unix.go b/pkg/machine/provider/platform_unix.go new file mode 100644 index 0000000000..d5e2318ae8 --- /dev/null +++ b/pkg/machine/provider/platform_unix.go @@ -0,0 +1,77 @@ +//go:build !windows && !darwin + +package provider + +import ( + "errors" + "fmt" + "io/fs" + "os" + + "github.com/containers/common/pkg/config" + "github.com/containers/podman/v5/pkg/machine/define" + "github.com/containers/podman/v5/pkg/machine/qemu" + "github.com/containers/podman/v5/pkg/machine/vmconfigs" + "github.com/sirupsen/logrus" +) + +func Get() (vmconfigs.VMProvider, error) { + cfg, err := config.Default() + if err != nil { + return nil, err + } + provider := cfg.Machine.Provider + if providerOverride, found := os.LookupEnv("CONTAINERS_MACHINE_PROVIDER"); found { + provider = providerOverride + } + resolvedVMType, err := define.ParseVMType(provider, define.QemuVirt) + if err != nil { + return nil, err + } + + logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String()) + switch resolvedVMType { + case define.QemuVirt: + return qemu.NewStubber() + default: + return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String()) + } +} + +func GetAll() []vmconfigs.VMProvider { + return []vmconfigs.VMProvider{new(qemu.QEMUStubber)} +} + +// SupportedProviders returns the providers that are supported on the host operating system +func SupportedProviders() []define.VMType { + return []define.VMType{define.QemuVirt} +} + +func IsInstalled(provider define.VMType) (bool, error) { + switch provider { + case define.QemuVirt: + cfg, err := config.Default() + if err != nil { + return false, err + } + if cfg == nil { + return false, fmt.Errorf("error fetching getting default config") + } + _, err = cfg.FindHelperBinary(qemu.QemuCommand, true) + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil + default: + return false, 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_windows.go b/pkg/machine/provider/platform_windows.go index 772693669d..eab7502af5 100644 --- a/pkg/machine/provider/platform_windows.go +++ b/pkg/machine/provider/platform_windows.go @@ -43,16 +43,11 @@ func Get() (vmconfigs.VMProvider, error) { } } -func GetAll(force bool) ([]vmconfigs.VMProvider, error) { - providers := []vmconfigs.VMProvider{ +func GetAll() []vmconfigs.VMProvider { + return []vmconfigs.VMProvider{ new(wsl.WSLStubber), + new(hyperv.HyperVStubber), } - if !wsl.HasAdminRights() && !force { - logrus.Warn("managing hyperv machines require admin authority.") - } else { - providers = append(providers, new(hyperv.HyperVStubber)) - } - return providers, nil } // SupportedProviders returns the providers that are supported on the host operating system @@ -60,20 +55,22 @@ 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) +func IsInstalled(provider define.VMType) (bool, error) { + switch provider { + case define.WSLVirt: + return wutil.IsWSLInstalled(), nil + case define.HyperVVirt: + service, err := hypervctl.NewLocalHyperVService() + if err == nil { + return true, nil + } + if service != nil { + defer service.Close() + } + return false, nil + default: + return false, nil } - service.Close() - - return installed, nil } // HasPermsForProvider returns whether the host operating system has the proper permissions to use the given provider