Machine init --provider

Add the ability for users to override the default provider when creating mahcines.  The new flag is `--provider` and allows you to specifiy a valid vmtype for the platform.  This PR also removes the previous list test where we tested listing all providers.  I added a PR for testing --provider which includes a standard `machine ls` which defaults now to showing all providers.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2025-10-03 14:01:49 -05:00
parent 386c8f3fe9
commit 5e1c2f8d7d
17 changed files with 162 additions and 119 deletions

View File

@@ -100,7 +100,7 @@ func hostInfo() (*entities.MachineHostInfo, error) {
host.Arch = runtime.GOARCH
host.OS = runtime.GOOS
dirs, err := env.GetMachineDirs(provider.VMType())
dirs, err := env.GetMachineDirs(machineProvider.VMType())
if err != nil {
return nil, err
}
@@ -126,7 +126,7 @@ func hostInfo() (*entities.MachineHostInfo, error) {
host.DefaultMachine = vm.Name
}
// If machine is running or starting, it is automatically the current machine
state, err := provider.State(vm, false)
state, err := machineProvider.State(vm, false)
if err != nil {
return nil, err
}
@@ -149,7 +149,7 @@ func hostInfo() (*entities.MachineHostInfo, error) {
}
}
host.VMType = provider.VMType().String()
host.VMType = machineProvider.VMType().String()
host.MachineImageDir = dirs.DataDir.GetPath()
host.MachineConfigDir = dirs.ConfigDir.GetPath()

View File

@@ -6,12 +6,15 @@ import (
"errors"
"fmt"
"os"
"slices"
"github.com/containers/podman/v6/cmd/podman/registry"
ldefine "github.com/containers/podman/v6/libpod/define"
"github.com/containers/podman/v6/libpod/events"
"github.com/containers/podman/v6/pkg/machine/define"
"github.com/containers/podman/v6/pkg/machine/provider"
"github.com/containers/podman/v6/pkg/machine/shim"
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
"github.com/shirou/gopsutil/v4/mem"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -36,6 +39,7 @@ var (
initOptionalFlags = InitOptionalFlags{}
defaultMachineName = define.DefaultMachineName
now bool
providerOverride string
)
// Flags which have a meaning when unspecified that differs from the flag default
@@ -158,6 +162,10 @@ func init() {
flags.BoolVar(&initOptionalFlags.tlsVerify, "tls-verify", true,
"Require HTTPS and verify certificates when contacting registries")
providerFlagName := "provider"
flags.StringVar(&providerOverride, providerFlagName, "", "Override the default machine provider")
_ = initCmd.RegisterFlagCompletionFunc(providerFlagName, autocompleteMachineProvider)
}
func initMachine(cmd *cobra.Command, args []string) error {
@@ -173,6 +181,21 @@ func initMachine(cmd *cobra.Command, args []string) error {
}
}
// If the provider option was given, we need to override the
// current provider
if cmd.Flags().Changed("provider") {
// var found bool
indexFunc := func(s vmconfigs.VMProvider) bool {
return s.VMType().String() == providerOverride
}
providers := provider.GetAll()
indexVal := slices.IndexFunc(providers, indexFunc)
if indexVal == -1 {
return fmt.Errorf("unsupported provider %q", providerOverride)
}
machineProvider = providers[indexVal]
}
// The vmtype names need to be reserved and cannot be used for podman machine names
if _, err := define.ParseVMType(initOpts.Name, define.UnknownVirt); err == nil {
return fmt.Errorf("cannot use %q for a machine name", initOpts.Name)
@@ -251,7 +274,7 @@ func initMachine(cmd *cobra.Command, args []string) error {
// return err
// }
err = shim.Init(initOpts, provider)
err = shim.Init(initOpts, machineProvider)
if err != nil {
// The installation is partially complete and podman should
// exit gracefully with no error and no success message.

View File

@@ -60,7 +60,7 @@ func inspect(cmd *cobra.Command, args []string) error {
continue
}
dirs, err := env.GetMachineDirs(provider.VMType())
dirs, err := env.GetMachineDirs(machineProvider.VMType())
if err != nil {
return err
}

View File

@@ -41,10 +41,7 @@ var (
}
)
var (
// TODO This needs to be deleted!
provider vmconfigs.VMProvider
)
var machineProvider vmconfigs.VMProvider
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
@@ -54,7 +51,7 @@ func init() {
func machinePreRunE(c *cobra.Command, args []string) error {
var err error
provider, err = provider2.Get()
machineProvider, err = provider2.Get()
if err != nil {
return err
}
@@ -104,6 +101,14 @@ func autocompleteMachine(_ *cobra.Command, args []string, toComplete string) ([]
return nil, cobra.ShellCompDirectiveNoFileComp
}
func autocompleteMachineProvider(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
suggestions := make([]string, 0)
for _, p := range provider2.GetAll() {
suggestions = append(suggestions, p.VMType().String())
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
provider, err := provider2.Get()

View File

@@ -96,6 +96,12 @@ Add the provided Ansible playbook to the machine and execute it after the first
Note: The playbook will be executed with the same privileges given to the user in the virtual machine. The playbook provided cannot include other files from the host system, as they will not be copied.
Use of the `--playbook` flag will require the image to include Ansible. The default image provided will have Ansible included.
#### **--provider**
Specify the provider for the machine to be created. This allows users to override the default provider on platforms
that have multiple providers.
#### **--rootful**
Whether this machine prefers rootful (`true`) or rootless (`false`)
@@ -237,6 +243,11 @@ Initialize the default Podman machine with a usb device passthrough with specifi
$ podman machine init --usb bus=1,devnum=3
```
Initialize a machine on different provider than the default
```
$ podman machine init --provider applehv
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**, **containers.conf(5)**

View File

@@ -22,6 +22,17 @@ environment variable while the machines are running can lead to unexpected behav
Podman machine behaviour can be modified via the [machine] section in the containers.conf(5) file.
Podman is based on virtual machine providers. The following table describes which providers are
supported by platform. The asterisk denotes the default provider for the platform.
| Platform | Provider |
| -------- |----------|
| Linux | qemu* |
| MacOS | libkrun* |
| MacOS | applehv |
| Windows | wsl* |
| Windows | hyperv |
## SUBCOMMANDS
| Command | Man Page | Description |

View File

@@ -1,14 +1,3 @@
package e2e_test
import "github.com/containers/podman/v6/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,7 +1,3 @@
package e2e_test
const podmanBinary = "../../../bin/podman-remote"
func getOtherProvider() string {
return ""
}

View File

@@ -10,22 +10,8 @@ import (
)
type initMachine struct {
/*
--cpus uint Number of CPUs (default 1)
--disk-size uint Disk size in GiB (default 100)
--ignition-path string Path to ignition file
--username string Username of the remote user (default "core" for FCOS, "user" for Fedora)
--image-path string Path to bootable image (default "testing")
-m, --memory uint Memory in MiB (default 2048)
--now Start machine now
--rootful Whether this machine should prefer rootful container execution
--playbook string Run an ansible playbook after first boot
--tls-verify Require HTTPS and verify certificates when contacting registries
--timezone string Set timezone (default "local")
-v, --volume stringArray Volumes to mount, source:target
--volume-driver string Optional volume driver
*/
playbook string
provider string
cpus *uint
diskSize *uint
swap *uint
@@ -80,6 +66,9 @@ func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
if l := len(i.playbook); l > 0 {
cmd = append(cmd, "--playbook", i.playbook)
}
if l := len(i.provider); l > 0 {
cmd = append(cmd, "--provider", i.provider)
}
if i.userModeNetworking {
cmd = append(cmd, "--user-mode-networking")
}
@@ -176,6 +165,11 @@ func (i *initMachine) withRunPlaybook(p string) *initMachine {
return i
}
func (i *initMachine) withProvider(p string) *initMachine {
i.provider = p
return i
}
func (i *initMachine) withTlsVerify(tlsVerify *bool) *initMachine {
i.tlsVerify = tlsVerify
return i

View File

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

View File

@@ -48,8 +48,3 @@ func (i *listMachine) withFormat(format string) *listMachine {
i.format = format
return i
}
func (i *listMachine) withAllProviders() *listMachine {
i.allProviders = true
return i
}

View File

@@ -7,8 +7,6 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega/gexec"
"github.com/containers/podman/v6/pkg/machine/define"
)
const podmanBinary = "../../../bin/windows/podman.exe"
@@ -29,15 +27,6 @@ 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 ""
}
func runWslCommand(cmdArgs []string) (*machineSession, error) {
binary := "wsl"
GinkgoWriter.Println(binary + " " + strings.Join(cmdArgs, " "))

View File

@@ -9,8 +9,11 @@ import (
"strings"
"time"
"github.com/containers/podman/v6/pkg/domain/entities"
"github.com/containers/podman/v6/pkg/machine/define"
"github.com/containers/podman/v6/pkg/machine/provider"
"github.com/containers/podman/v6/utils"
jsoniter "github.com/json-iterator/go"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
@@ -635,6 +638,68 @@ var _ = Describe("podman machine init", func() {
Expect(err).ToNot(HaveOccurred())
Expect(proc2Session.outputToString()).To(ContainSubstring("/proc/sys/fs/binfmt_misc/qemu-x86_64"))
})
It("init machine with invalid --provider for platform", func() {
var providerOverride string
switch testProvider.VMType() {
case define.QemuVirt, define.AppleHvVirt, define.LibKrun:
providerOverride = "wsl"
case define.WSLVirt, define.HyperVVirt:
providerOverride = "applehv"
default:
Fail("unsupported provider in tests")
}
i := initMachine{}
machineName := randomString()
session, err := mb.setName(machineName).setCmd(i.withImage(mb.imagePath).withProvider(providerOverride)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session.errorToString()).To(ContainSubstring(fmt.Sprintf("unsupported provider %q", providerOverride)))
})
It("machine init --provider", func() {
skipIfVmtype(define.WSLVirt, "Skip to avoid long tests or image pulls on Windows")
skipIfVmtype(define.HyperVVirt, "Skip to avoid long tests or image pulls Windows")
verify := make(map[string]string)
// Loop all providers and create a map with
// machine_name -> provider
for _, p := range provider.GetAll() {
machineName := randomString()
verify[machineName] = p.VMType().String()
}
// Loop verify and create a podman machine with the name and
// --provider
for name, p := range verify {
i := initMachine{}
session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath).withProvider(p)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session.ExitCode()).To(Equal(0))
}
// List machines and marshall them
list := new(listMachine)
list = list.withFormat("json")
listSession, err := mb.setCmd(list).run()
Expect(err).NotTo(HaveOccurred())
var listResponse []*entities.ListReporter
err = jsoniter.Unmarshal(listSession.Bytes(), &listResponse)
Expect(err).NotTo(HaveOccurred())
Expect(listResponse).To(HaveLen(len(verify)))
// Loop our machine list and make sure we have a name -> provider
// match
for _, l := range listResponse {
p, ok := verify[l.Name]
if !ok {
Fail(fmt.Sprintf("%s not found in list", l.Name))
}
Expect(p).To(Equal(l.VMType))
}
})
})
var p4Config = []byte(`{

View File

@@ -1,14 +1,12 @@
package e2e_test
import (
"os"
"slices"
"strconv"
"strings"
"time"
"github.com/containers/podman/v6/pkg/domain/entities"
"github.com/containers/podman/v6/pkg/machine/define"
jsoniter "github.com/json-iterator/go"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@@ -184,51 +182,6 @@ 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() {
Skip("This test will be changed for when --provider is added to init")
// create machine on other provider
currprovider := os.Getenv("CONTAINERS_MACHINE_PROVIDER")
os.Setenv("CONTAINERS_MACHINE_PROVIDER", getOtherProvider())
defer os.Setenv("CONTAINERS_MACHINE_PROVIDER", currprovider)
othermach := new(initMachine)
if !isWSL() && !isVmtype(define.HyperVVirt) {
// This would need to fetch a new image as we cannot use the image from the other provider,
// to avoid big pulls which are slow and flaky use /dev/null which works on macos and qemu
// as we never run the image if we do not start it.
othermach.withImage(os.DevNull)
}
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(listNames).To(ContainElements("otherprovider", name))
})
})
func stripAsterisk(sl []string) {

View File

@@ -33,6 +33,12 @@ func Get() (vmconfigs.VMProvider, error) {
}
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
return GetByVMType(resolvedVMType)
}
// GetByVMType takes a VMType (presumably from ParseVMType) and returns the correlating
// VMProvider
func GetByVMType(resolvedVMType define.VMType) (vmconfigs.VMProvider, error) {
switch resolvedVMType {
case define.AppleHvVirt:
return new(applehv.AppleHVStubber), nil
@@ -42,8 +48,8 @@ func Get() (vmconfigs.VMProvider, error) {
}
return new(libkrun.LibKrunStubber), nil
default:
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}
func GetAll() []vmconfigs.VMProvider {

View File

@@ -30,18 +30,24 @@ func Get() (vmconfigs.VMProvider, error) {
}
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())
}
return GetByVMType(resolvedVMType)
}
func GetAll() []vmconfigs.VMProvider {
return []vmconfigs.VMProvider{new(qemu.QEMUStubber)}
}
// GetByVMType takes a VMType (presumably from ParseVMType) and returns the correlating
// VMProvider
func GetByVMType(resolvedVMType define.VMType) (vmconfigs.VMProvider, error) {
switch resolvedVMType {
case define.QemuVirt:
return qemu.NewStubber()
default:
}
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}

View File

@@ -5,12 +5,11 @@ import (
"os"
"github.com/containers/libhvee/pkg/hypervctl"
"github.com/containers/podman/v6/pkg/machine/define"
"github.com/containers/podman/v6/pkg/machine/hyperv"
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
"github.com/containers/podman/v6/pkg/machine/wsl"
"github.com/containers/podman/v6/pkg/machine/wsl/wutil"
"github.com/containers/podman/v6/pkg/machine/define"
"github.com/containers/podman/v6/pkg/machine/hyperv"
"github.com/sirupsen/logrus"
"go.podman.io/common/pkg/config"
)
@@ -28,8 +27,13 @@ func Get() (vmconfigs.VMProvider, error) {
if err != nil {
return nil, err
}
logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String())
return GetByVMType(resolvedVMType)
}
// GetByVMType takes a VMType (presumably from ParseVMType) and returns the correlating
// VMProvider
func GetByVMType(resolvedVMType define.VMType) (vmconfigs.VMProvider, error) {
switch resolvedVMType {
case define.WSLVirt:
return new(wsl.WSLStubber), nil
@@ -39,8 +43,8 @@ func Get() (vmconfigs.VMProvider, error) {
}
return new(hyperv.HyperVStubber), nil
default:
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())
}
func GetAll() []vmconfigs.VMProvider {