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 <acui@redhat.com>
This commit is contained in:
Ashley Cui
2024-08-21 09:30:46 -04:00
parent 43fe3ebaf3
commit 41f945fc24
12 changed files with 261 additions and 128 deletions

View File

@ -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 {

View File

@ -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{})

View File

@ -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'

View File

@ -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 ""
}

View File

@ -1,3 +1,7 @@
package e2e_test
const podmanBinary = "../../../bin/podman-remote"
func getOtherProvider() string {
return ""
}

View File

@ -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
}

View File

@ -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 ""
}

View File

@ -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) {

View File

@ -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
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())
}
}
_, 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
return installedTypes, nil
}

View File

@ -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
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
}
if appleHvInstalled {
installed = append(installed, define.AppleHvVirt)
}
libKrunInstalled, err := libKrunInstalled()
if err != nil {
return nil, err
}
if libKrunInstalled {
installed = append(installed, define.LibKrun)
}
return installed, nil
}
func appleHvInstalled() (bool, error) {

View File

@ -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
}

View File

@ -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)
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, 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