Skip to content

Commit

Permalink
machine: Add -all-providers flag to machine list
Browse files Browse the repository at this point in the history
Podman machine list now supports a new option, --all-providers, which lists all machines from all providers.

Signed-off-by: Ashley Cui <[email protected]>
  • Loading branch information
ashley-cui committed Aug 28, 2024
1 parent 43fe3eb commit 41f945f
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 128 deletions.
60 changes: 41 additions & 19 deletions cmd/podman/machine/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand All @@ -40,9 +44,10 @@ var (
)

type listFlagType struct {
format string
noHeading bool
quiet bool
format string
noHeading bool
quiet bool
allProviders bool
}

func init() {
Expand All @@ -57,15 +62,26 @@ 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 {
var (
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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion cmd/podman/machine/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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{})
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-machine-list.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
11 changes: 11 additions & 0 deletions pkg/machine/e2e/config_darwin_test.go
Original file line number Diff line number Diff line change
@@ -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 ""
}
4 changes: 4 additions & 0 deletions pkg/machine/e2e/config_linux_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
package e2e_test

const podmanBinary = "../../../bin/podman-remote"

func getOtherProvider() string {
return ""
}
16 changes: 13 additions & 3 deletions pkg/machine/e2e/config_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
11 changes: 11 additions & 0 deletions pkg/machine/e2e/config_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"os/exec"
"strings"

"github.com/containers/podman/v5/pkg/machine/define"
)

const podmanBinary = "../../../bin/windows/podman.exe"
Expand All @@ -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 ""
}
43 changes: 43 additions & 0 deletions pkg/machine/e2e/list_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
73 changes: 11 additions & 62 deletions pkg/machine/provider/platform.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 41f945f

Please sign in to comment.