podman5 machine reconfig 1

this is a logical place to get changes upstream before they grow out of
control.  this pr is the first in an effort to deduplicate machine code
and streamline code flow.

a lot of code is simply moved to eliminate circular imports.  names and
specific paths can ultimately be changed.  i dont like some of the
descriptive interface names, etc.  ultimately, i think once we have the
"old" code sanitized, we can re-use some of those.

clearly some of what is in here is temporary and will either be deleted,
changed, or moved again as this effort comes to a close.

right now, the machine code does not use any of the "new" code.  you
will see in `init` and `rm` some commented out code that hooks it. i'm
afraid things will get worse before they get better (way worse).

[NO NEW TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2024-01-01 08:40:36 -06:00
parent c89dd4a92f
commit b9bcfa4749
33 changed files with 754 additions and 142 deletions

View File

@ -26,7 +26,7 @@ var (
ValidArgsFunction: completion.AutocompleteNone,
}
initOpts = machine.InitOptions{}
initOpts = define.InitOptions{}
initOptionalFlags = InitOptionalFlags{}
defaultMachineName = machine.DefaultMachineName
now bool
@ -187,6 +187,20 @@ func initMachine(cmd *cobra.Command, args []string) error {
// Finished = *, err != nil - Exit with an error message
return err
}
// The following is for enabling podman machine approach
/*
s := new(p5qemu.QEMUStubber)
mc, err := p5.Init(initOpts, s)
if err != nil {
return err
}
// TODO callback needed for the configuration file
if err := mc.Write(); err != nil {
return err
}
*/
newMachineEvent(events.Init, events.Event{Name: initOpts.Name})
fmt.Println("Machine init complete")

View File

@ -64,6 +64,15 @@ func list(cmd *cobra.Command, args []string) error {
err error
)
// Podman 5 development
/*
s := new(p5qemu.QEMUStubber)
if err := p5.List([]vmconfigs.VMStubber{s}); err != nil {
return err
}
*/
listResponse, err = provider.List(opts)
if err != nil {
return fmt.Errorf("listing vms: %w", err)

2
go.mod
View File

@ -29,6 +29,7 @@ require (
github.com/docker/go-connections v0.5.0
github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651
github.com/docker/go-units v0.5.0
github.com/go-openapi/errors v0.21.0
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
github.com/google/gofuzz v1.2.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@ -119,7 +120,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.21.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect

View File

@ -112,7 +112,7 @@ func (v AppleHVVirtualization) LoadVMByName(name string) (machine.VM, error) {
return m.loadFromFile()
}
func (v AppleHVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (v AppleHVVirtualization) NewMachine(opts define.InitOptions) (machine.VM, error) {
m := MacMachine{Name: opts.Name}
if len(opts.USBs) > 0 {

View File

@ -22,6 +22,7 @@ import (
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/applehv/vfkit"
"github.com/containers/podman/v4/pkg/machine/connection"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/ignition"
"github.com/containers/podman/v4/pkg/machine/sockets"
@ -129,7 +130,7 @@ func (m *MacMachine) setVfkitInfo(cfg *config.Config, readySocket define.VMFile)
// addMountsToVM converts the volumes passed through the CLI to virtio-fs mounts
// and adds them to the machine
func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error {
func (m *MacMachine) addMountsToVM(opts define.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error {
var mounts []vmconfigs.Mount
for _, volume := range opts.Volumes {
source, target, _, readOnly, err := machine.ParseVolumeFromPath(volume)
@ -145,7 +146,7 @@ func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]mac
return nil
}
func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
func (m *MacMachine) Init(opts define.InitOptions) (bool, error) {
var (
key string
virtiofsMnts []machine.VirtIoFs
@ -225,7 +226,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
return false, err
}
err = machine.AddSSHConnectionsToPodmanSocket(
err = connection.AddSSHConnectionsToPodmanSocket(
m.UID,
m.Port,
m.IdentityPath,
@ -294,7 +295,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
}
func (m *MacMachine) removeSystemConnections() error {
return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
return connection.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
}
func (m *MacMachine) Inspect() (*machine.InspectInfo, error) {
@ -385,7 +386,7 @@ func (m *MacMachine) Remove(name string, opts machine.RemoveOptions) (string, fu
confirmationMessage += "\n"
return confirmationMessage, func() error {
machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
connection.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
// TODO We will need something like this for applehv too i think
/*
// Remove the HVSOCK for networking

View File

@ -22,36 +22,14 @@ import (
"github.com/sirupsen/logrus"
)
type InitOptions struct {
CPUS uint64
DiskSize uint64
IgnitionPath string
ImagePath string
Volumes []string
VolumeDriver string
IsDefault bool
Memory uint64
Name string
TimeZone string
URI url.URL
Username string
ReExec bool
Rootful bool
UID string // uid of the user that called machine
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
USBs []string
}
const (
DefaultMachineName string = "podman-machine-default"
apiUpTimeout = 20 * time.Second
)
type RemoteConnectionType string
var (
SSHRemoteConnection RemoteConnectionType = "ssh"
ForwarderBinaryName = "gvproxy"
DefaultIgnitionUserName = "core"
ForwarderBinaryName = "gvproxy"
)
type Download struct {
@ -120,7 +98,7 @@ type RemoveOptions struct {
type InspectOptions struct{}
type VM interface {
Init(opts InitOptions) (bool, error)
Init(opts define.InitOptions) (bool, error)
Inspect() (*InspectInfo, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
Set(name string, opts SetOptions) ([]error, error)
@ -130,24 +108,6 @@ type VM interface {
Stop(name string, opts StopOptions) error
}
func GetLock(name string, vmtype define.VMType) (*lockfile.LockFile, error) {
// FIXME: there's a painful amount of `GetConfDir` calls scattered
// across the code base. This should be done once and stored
// somewhere instead.
vmConfigDir, err := GetConfDir(vmtype)
if err != nil {
return nil, err
}
lockPath := filepath.Join(vmConfigDir, name+".lock")
lock, err := lockfile.GetLockFile(lockPath)
if err != nil {
return nil, fmt.Errorf("creating lockfile for VM: %w", err)
}
return lock, nil
}
type DistributionDownload interface {
HasUsableCache() (bool, error)
Get() *Download
@ -167,26 +127,6 @@ type InspectInfo struct {
Rootful bool
}
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
// TODO Should this function have input verification?
userInfo := url.User(userName)
uri := url.URL{
Scheme: "ssh",
Opaque: "",
User: userInfo,
Host: host,
Path: path,
RawPath: "",
ForceQuery: false,
RawQuery: "",
Fragment: "",
}
if len(port) > 0 {
uri.Host = net.JoinHostPort(uri.Hostname(), port)
}
return uri
}
// GetCacheDir returns the dir where VM images are downloaded into when pulled
func GetCacheDir(vmType define.VMType) (string, error) {
dataDir, err := GetDataDir(vmType)
@ -226,6 +166,26 @@ func GetGlobalDataDir() (string, error) {
return dataDir, os.MkdirAll(dataDir, 0755)
}
func GetMachineDirs(vmType define.VMType) (*define.MachineDirs, error) {
rtDir, err := getRuntimeDir()
if err != nil {
return nil, err
}
rtDir = filepath.Join(rtDir, "podman")
configDir, err := GetConfDir(vmType)
if err != nil {
return nil, err
}
dataDir, err := GetDataDir(vmType)
dirs := define.MachineDirs{
ConfigDir: configDir,
DataDir: dataDir,
RuntimeDir: rtDir,
}
return &dirs, err
}
// DataDirPrefix returns the path prefix for all machine data files
func DataDirPrefix() (string, error) {
data, err := homedir.GetDataHome()
@ -307,7 +267,7 @@ type VirtProvider interface { //nolint:interfacebloat
IsValidVMName(name string) (bool, error)
List(opts ListOptions) ([]*ListResponse, error)
LoadVMByName(name string) (VM, error)
NewMachine(opts InitOptions) (VM, error)
NewMachine(opts define.InitOptions) (VM, error)
NewDownload(vmName string) (Download, error)
RemoveAndCleanMachines() error
VMType() define.VMType
@ -465,3 +425,22 @@ func (dl Download) AcquireVMImage(imagePath string) (*define.VMFile, FCOSStream,
}
return imageLocation, fcosStream, nil
}
// Deprecated: GetLock
func GetLock(name string, vmtype define.VMType) (*lockfile.LockFile, error) {
// FIXME: there's a painful amount of `GetConfDir` calls scattered
// across the code base. This should be done once and stored
// somewhere instead.
vmConfigDir, err := GetConfDir(vmtype)
if err != nil {
return nil, err
}
lockPath := filepath.Join(vmConfigDir, name+".lock")
lock, err := lockfile.GetLockFile(lockPath)
if err != nil {
return nil, fmt.Errorf("creating lockfile for VM: %w", err)
}
return lock, nil
}

View File

@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/containers/podman/v4/pkg/machine/connection"
)
func TestRemoteConnectionType_MakeSSHURL(t *testing.T) {
@ -27,7 +28,7 @@ func TestRemoteConnectionType_MakeSSHURL(t *testing.T) {
}
tests := []struct {
name string
rc RemoteConnectionType
rc connection.RemoteConnectionType
args args
want url.URL
}{

View File

@ -0,0 +1,36 @@
package connection
import (
"fmt"
"net/url"
"strconv"
"github.com/containers/podman/v4/pkg/machine/define"
)
// AddSSHConnectionsToPodmanSocket adds SSH connections to the podman socket if
// no ignition path is provided
func AddSSHConnectionsToPodmanSocket(uid, port int, identityPath, name, remoteUsername string, opts define.InitOptions) error {
if len(opts.IgnitionPath) > 0 {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
return nil
}
uri := SSHRemoteConnection.MakeSSHURL(LocalhostIP, fmt.Sprintf("/run/user/%d/podman/podman.sock", uid), strconv.Itoa(port), remoteUsername)
uriRoot := SSHRemoteConnection.MakeSSHURL(LocalhostIP, "/run/podman/podman.sock", strconv.Itoa(port), "root")
uris := []url.URL{uri, uriRoot}
names := []string{name, name + "-root"}
// The first connection defined when connections is empty will become the default
// regardless of IsDefault, so order according to rootful
if opts.Rootful {
uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
}
for i := 0; i < 2; i++ {
if err := AddConnection(&uris[i], names[i], identityPath, opts.IsDefault && i == 0); err != nil {
return err
}
}
return nil
}

View File

@ -1,10 +1,12 @@
//go:build amd64 || arm64
package machine
package connection
import (
"errors"
"fmt"
"net"
"net/url"
"os"
"github.com/containers/common/pkg/config"
@ -105,3 +107,28 @@ func RemoveFilesAndConnections(files []string, names ...string) {
logrus.Error(err)
}
}
type RemoteConnectionType string
var SSHRemoteConnection RemoteConnectionType = "ssh"
// MakeSSHURL
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
// TODO Should this function have input verification?
userInfo := url.User(userName)
uri := url.URL{
Scheme: "ssh",
Opaque: "",
User: userInfo,
Host: host,
Path: path,
RawPath: "",
ForceQuery: false,
RawQuery: "",
Fragment: "",
}
if len(port) > 0 {
uri.Host = net.JoinHostPort(uri.Hostname(), port)
}
return uri
}

View File

@ -1,4 +1,21 @@
package define
import "os"
const UserCertsTargetPath = "/etc/containers/certs.d"
const DefaultIdentityName = "machine"
var (
DefaultFilePerm os.FileMode = 0644
)
type CreateVMOpts struct {
Name string
Dirs *MachineDirs
}
type MachineDirs struct {
ConfigDir string
DataDir string
RuntimeDir string
}

View File

@ -0,0 +1,23 @@
package define
import "net/url"
type InitOptions struct {
CPUS uint64
DiskSize uint64
IgnitionPath string
ImagePath string
Volumes []string
VolumeDriver string
IsDefault bool
Memory uint64
Name string
TimeZone string
URI url.URL
Username string
ReExec bool
Rootful bool
UID string // uid of the user that called machine
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
USBs []string
}

View File

@ -116,7 +116,7 @@ func (v HyperVVirtualization) LoadVMByName(name string) (machine.VM, error) {
return m.loadFromFile()
}
func (v HyperVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (v HyperVVirtualization) NewMachine(opts define.InitOptions) (machine.VM, error) {
m := HyperVMachine{Name: opts.Name}
if len(opts.ImagePath) < 1 {
return nil, errors.New("must define --image-path for hyperv support")

View File

@ -20,6 +20,7 @@ import (
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/libhvee/pkg/hypervctl"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/connection"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/hyperv/vsock"
"github.com/containers/podman/v4/pkg/machine/ignition"
@ -129,7 +130,7 @@ func (m *HyperVMachine) readAndSplitIgnition() error {
return vm.SplitAndAddIgnition("ignition.config.", reader)
}
func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
func (m *HyperVMachine) Init(opts define.InitOptions) (bool, error) {
var (
key string
err error
@ -192,7 +193,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
m.Port = sshPort
m.RemoteUsername = opts.Username
err = machine.AddSSHConnectionsToPodmanSocket(
err = connection.AddSSHConnectionsToPodmanSocket(
m.UID,
m.Port,
m.IdentityPath,
@ -315,7 +316,7 @@ func (m *HyperVMachine) unregisterMachine() error {
}
func (m *HyperVMachine) removeSystemConnections() error {
return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
return connection.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
}
func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
@ -441,7 +442,7 @@ func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, fu
confirmationMessage += "\n"
return confirmationMessage, func() error {
machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
connection.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
m.removeNetworkAndReadySocketsFromRegistry()
if err := vm.Remove(""); err != nil {
return fmt.Errorf("removing virtual machine: %w", err)

17
pkg/machine/lock/lock.go Normal file
View File

@ -0,0 +1,17 @@
package lock
import (
"fmt"
"path/filepath"
"github.com/containers/storage/pkg/lockfile"
)
func GetMachineLock(name string, machineConfigDir string) (*lockfile.LockFile, error) {
lockPath := filepath.Join(machineConfigDir, name+".lock")
lock, err := lockfile.GetLockFile(lockPath)
if err != nil {
return nil, fmt.Errorf("creating lockfile for VM: %w", err)
}
return lock, nil
}

View File

@ -5,10 +5,9 @@ package machine
import (
"encoding/json"
"fmt"
"net/url"
"os"
"strconv"
"github.com/containers/podman/v4/pkg/machine/connection"
"github.com/containers/storage/pkg/ioutils"
)
@ -30,33 +29,6 @@ func GetDevNullFiles() (*os.File, *os.File, error) {
return dnr, dnw, nil
}
// AddSSHConnectionsToPodmanSocket adds SSH connections to the podman socket if
// no ignition path is provided
func AddSSHConnectionsToPodmanSocket(uid, port int, identityPath, name, remoteUsername string, opts InitOptions) error {
if len(opts.IgnitionPath) > 0 {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
return nil
}
uri := SSHRemoteConnection.MakeSSHURL(LocalhostIP, fmt.Sprintf("/run/user/%d/podman/podman.sock", uid), strconv.Itoa(port), remoteUsername)
uriRoot := SSHRemoteConnection.MakeSSHURL(LocalhostIP, "/run/podman/podman.sock", strconv.Itoa(port), "root")
uris := []url.URL{uri, uriRoot}
names := []string{name, name + "-root"}
// The first connection defined when connections is empty will become the default
// regardless of IsDefault, so order according to rootful
if opts.Rootful {
uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
}
for i := 0; i < 2; i++ {
if err := AddConnection(&uris[i], names[i], identityPath, opts.IsDefault && i == 0); err != nil {
return err
}
}
return nil
}
// WaitAPIAndPrintInfo prints info about the machine and does a ping test on the
// API socket
func WaitAPIAndPrintInfo(forwardState APIForwardingState, name, helper, forwardSock string, noInfo, isIncompatible, rootful bool) {
@ -158,7 +130,7 @@ following command in your terminal session:
// SetRootful modifies the machine's default connection to be either rootful or
// rootless
func SetRootful(rootful bool, name, rootfulName string) error {
return UpdateConnectionIfDefault(rootful, name, rootfulName)
return connection.UpdateConnectionIfDefault(rootful, name, rootfulName)
}
// WriteConfig writes the machine's JSON config file

View File

@ -0,0 +1,11 @@
package machine
import "os"
func getRuntimeDir() (string, error) {
tmpDir, ok := os.LookupEnv("TMPDIR")
if !ok {
tmpDir = "/tmp"
}
return tmpDir, nil
}

View File

@ -0,0 +1,11 @@
package machine
import "os"
func getRuntimeDir() (string, error) {
tmpDir, ok := os.LookupEnv("TMPDIR")
if !ok {
tmpDir = "/tmp"
}
return tmpDir, nil
}

View File

@ -0,0 +1,13 @@
package machine
import (
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/pkg/util"
)
func getRuntimeDir() (string, error) {
if !rootless.IsRootless() {
return "/run", nil
}
return util.GetRootlessRuntimeDir()
}

View File

@ -0,0 +1,11 @@
package machine
import "os"
func getRuntimeDir() (string, error) {
tmpDir, ok := os.LookupEnv("TEMP")
if !ok {
tmpDir = os.Getenv("LOCALAPPDATA") + "\\Temp"
}
return tmpDir, nil
}

134
pkg/machine/p5/host.go Normal file
View File

@ -0,0 +1,134 @@
package p5
import (
"context"
"encoding/json"
"fmt"
"maps"
"github.com/containers/podman/v4/pkg/machine"
machineDefine "github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/ocipull"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
"github.com/sirupsen/logrus"
)
/*
Host
├ Info
├ OS Apply
├ SSH
├ List
├ Init
├ VMExists
├ CheckExclusiveActiveVM *HyperV/WSL need to check their hypervisors as well
*/
func Info() {}
func OSApply() {}
func SSH() {}
// List is done at the host level to allow for a *possible* future where
// more than one provider is used
func List(vmstubbers []vmconfigs.VMStubber) error {
mcs, err := getMCs(vmstubbers)
if err != nil {
return err
}
fmt.Println("machines")
for name, mc := range mcs {
logrus.Debugf("found machine -> %q %q", name, mc.Created)
}
fmt.Println("machines end")
return nil
}
func Init(opts machineDefine.InitOptions, mp vmconfigs.VMStubber) (*vmconfigs.MachineConfig, error) {
dirs, err := machine.GetMachineDirs(mp.VMType())
if err != nil {
return nil, err
}
fmt.Println("/// begin init")
mc, err := vmconfigs.NewMachineConfig(opts, dirs.ConfigDir)
if err != nil {
return nil, err
}
createOpts := machineDefine.CreateVMOpts{
Name: opts.Name,
Dirs: dirs,
}
// Get Image
// TODO This needs rework bigtime; my preference is most of below of not living in here.
versionedOCIDownload, err := ocipull.NewVersioned(context.Background(), dirs.DataDir, opts.Name, mp.VMType().String())
if err != nil {
return nil, err
}
if err := versionedOCIDownload.Pull(); err != nil {
return nil, err
}
unpacked, err := versionedOCIDownload.Unpack()
if err != nil {
return nil, err
}
defer func() {
logrus.Debugf("cleaning up %q", unpacked.GetPath())
if err := unpacked.Delete(); err != nil {
logrus.Errorf("unable to delete local compressed file %q:%v", unpacked.GetPath(), err)
}
}()
imagePath, err := versionedOCIDownload.Decompress(unpacked)
if err != nil {
return nil, err
}
mc.ImagePath = imagePath
// TODO needs callback to remove image
logrus.Debugf("--> imagePath is %q", imagePath.GetPath())
// TODO development only -- set to qemu provider
if err := mp.CreateVM(createOpts, mc); err != nil {
return nil, err
}
b, err := json.MarshalIndent(mc, "", " ")
if err != nil {
return nil, err
}
fmt.Println(string(b))
fmt.Println("/// end init")
return mc, nil
}
// VMExists looks across given providers for a machine's existence. returns the actual config and found bool
func VMExists(name string, vmstubbers []vmconfigs.VMStubber) (*vmconfigs.MachineConfig, bool, error) {
mcs, err := getMCs(vmstubbers)
if err != nil {
return nil, false, err
}
mc, found := mcs[name]
return mc, found, nil
}
func CheckExclusiveActiveVM() {}
func getMCs(vmstubbers []vmconfigs.VMStubber) (map[string]*vmconfigs.MachineConfig, error) {
mcs := make(map[string]*vmconfigs.MachineConfig)
for _, stubber := range vmstubbers {
dirs, err := machine.GetMachineDirs(stubber.VMType())
if err != nil {
return nil, err
}
stubberMCs, err := vmconfigs.LoadMachinesInDir(dirs.ConfigDir)
if err != nil {
return nil, err
}
maps.Copy(mcs, stubberMCs)
}
return mcs, nil
}

View File

@ -2,7 +2,9 @@ package command
import (
"encoding/base64"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
@ -14,6 +16,12 @@ import (
"github.com/containers/podman/v4/pkg/machine/define"
)
// defaultQMPTimeout is the timeout duration for the
// qmp monitor interactions.
var (
defaultQMPTimeout = 2 * time.Second
)
// QemuCmd is an alias around a string slice to prevent the need to migrate the
// MachineVM struct due to changes
type QemuCmd []string
@ -234,3 +242,22 @@ type Monitor struct {
// Timeout in seconds for qmp monitor transactions
Timeout time.Duration
}
// NewQMPMonitor creates the monitor subsection of our vm
func NewQMPMonitor(name, machineRuntimeDir string) (Monitor, error) {
if _, err := os.Stat(machineRuntimeDir); errors.Is(err, fs.ErrNotExist) {
if err := os.MkdirAll(machineRuntimeDir, 0755); err != nil {
return Monitor{}, err
}
}
address, err := define.NewMachineFile(filepath.Join(machineRuntimeDir, "qmp_"+name+".sock"), nil)
if err != nil {
return Monitor{}, err
}
monitor := Monitor{
Network: "unix",
Address: *address,
Timeout: defaultQMPTimeout,
}
return monitor, nil
}

View File

@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/compression"
@ -16,7 +18,6 @@ import (
"github.com/containers/podman/v4/pkg/machine/ignition"
"github.com/containers/podman/v4/pkg/machine/qemu/command"
"github.com/containers/podman/v4/pkg/machine/sockets"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
"github.com/containers/podman/v4/utils"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
@ -51,7 +52,7 @@ func findQEMUBinary() (string, error) {
// setQMPMonitorSocket sets the virtual machine's QMP Monitor socket
func (v *MachineVM) setQMPMonitorSocket() error {
monitor, err := NewQMPMonitor("unix", v.Name, defaultQMPTimeout)
monitor, err := newQMPMonitor("unix", v.Name, defaultQMPTimeout)
if err != nil {
return err
}
@ -74,7 +75,7 @@ func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCM
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (p *QEMUVirtualization) NewMachine(opts define.InitOptions) (machine.VM, error) {
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
@ -159,7 +160,7 @@ func (p *QEMUVirtualization) LoadVMByName(name string) (machine.VM, error) {
return nil, err
}
lock, err := machine.GetLock(vm.Name, vmtype)
lock, err := machine.GetLock(vm.Name, vmtype) //nolint:staticcheck
if err != nil {
return nil, err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/containers/common/pkg/config"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/connection"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/ignition"
"github.com/containers/podman/v4/pkg/machine/qemu/command"
@ -84,7 +85,7 @@ type MachineVM struct {
// addMountsToVM converts the volumes passed through the CLI into the specified
// volume driver and adds them to the machine
func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error {
func (v *MachineVM) addMountsToVM(opts define.InitOptions) error {
var volumeType string
switch opts.VolumeDriver {
// "" is the default volume driver
@ -112,7 +113,7 @@ func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error {
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
func (v *MachineVM) Init(opts define.InitOptions) (bool, error) {
var (
key string
err error
@ -150,7 +151,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// Add location of bootable image
v.CmdLine.SetBootableImage(v.getImageFile())
if err = machine.AddSSHConnectionsToPodmanSocket(
if err = connection.AddSSHConnectionsToPodmanSocket(
v.UID,
v.Port,
v.IdentityPath,
@ -234,7 +235,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
}
func (v *MachineVM) removeSystemConnections() error {
return machine.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
return connection.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
}
func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
@ -877,8 +878,8 @@ func (v *MachineVM) stopLocked() error {
return nil
}
// NewQMPMonitor creates the monitor subsection of our vm
func NewQMPMonitor(network, name string, timeout time.Duration) (command.Monitor, error) {
// Deprecated: newQMPMonitor creates the monitor subsection of our vm
func newQMPMonitor(network, name string, timeout time.Duration) (command.Monitor, error) {
rtDir, err := getRuntimeDir()
if err != nil {
return command.Monitor{}, err
@ -991,7 +992,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func()
confirmationMessage += "\n"
return confirmationMessage, func() error {
machine.RemoveFilesAndConnections(files, v.Name, v.Name+"-root")
connection.RemoveFilesAndConnections(files, v.Name, v.Name+"-root")
return nil
}, nil
}

View File

@ -0,0 +1,69 @@
package p5qemu
import (
"fmt"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/qemu/command"
"github.com/containers/podman/v4/pkg/machine/vmconfigs"
"github.com/go-openapi/errors"
)
type QEMUStubber struct {
vmconfigs.QEMUConfig
}
func (q *QEMUStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig) error {
fmt.Println("//// CreateVM: ", opts.Name)
monitor, err := command.NewQMPMonitor(opts.Name, opts.Dirs.RuntimeDir)
if err != nil {
return err
}
qemuConfig := vmconfigs.QEMUConfig{
Command: nil,
QMPMonitor: monitor,
}
mc.QEMUHypervisor = &qemuConfig
return nil
}
func (q *QEMUStubber) StartVM() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) StopVM() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) InspectVM() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) RemoveVM() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) ChangeSettings() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) IsFirstBoot() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) SetupMounts() error {
return errors.NotImplemented("")
}
func (q *QEMUStubber) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", errors.NotImplemented("")
}
func (q *QEMUStubber) GetHyperVisorVMs() ([]string, error) {
return nil, nil
}
func (q *QEMUStubber) VMType() define.VMType {
return define.QemuVirt
}

View File

@ -0,0 +1 @@
package machine

View File

@ -11,8 +11,6 @@ import (
"github.com/containers/storage/pkg/lockfile"
)
type aThing struct{}
type MachineConfig struct {
// Common stuff
Created time.Time
@ -32,6 +30,8 @@ type MachineConfig struct {
// Image stuff
imageDescription machineImage //nolint:unused
ImagePath *define.VMFile // Temporary only until a proper image struct is worked out
// Provider stuff
AppleHypervisor *AppleHVConfig `json:",omitempty"`
QEMUHypervisor *QEMUConfig `json:",omitempty"`
@ -39,11 +39,14 @@ type MachineConfig struct {
WSLHypervisor *WSLConfig `json:",omitempty"`
lock *lockfile.LockFile //nolint:unused
// configPath can be used for reading, writing, removing
configPath *define.VMFile
}
// MachineImage describes a podman machine image
type MachineImage struct {
OCI *ociMachineImage
OCI *OCIMachineImage
FCOS *fcosMachineImage
}
@ -63,7 +66,7 @@ type machineImage interface { //nolint:unused
path() string
}
type ociMachineImage struct {
type OCIMachineImage struct {
// registry
// TODO JSON serial/deserial will write string to disk
// but in code it is a types.ImageReference
@ -72,11 +75,11 @@ type ociMachineImage struct {
FQImageReference string
}
func (o ociMachineImage) path() string {
func (o OCIMachineImage) path() string {
return ""
}
func (o ociMachineImage) download() error {
func (o OCIMachineImage) download() error {
return nil
}
@ -94,6 +97,13 @@ func (f fcosMachineImage) path() string {
return ""
}
type VMStubber interface {
CreateVM(opts define.CreateVMOpts, mc *MachineConfig) error
VMType() define.VMType
GetHyperVisorVMs() ([]string, error)
}
type aThing struct{}
// HostUser describes the host user
type HostUser struct {
// Whether this machine should run in a rootful or rootless manner

View File

@ -1,6 +1,8 @@
package vmconfigs
import (
"os"
"github.com/containers/podman/v4/pkg/machine/applehv/vfkit"
)
@ -13,3 +15,7 @@ type AppleHVConfig struct {
type HyperVConfig struct{}
type WSLConfig struct{}
type QEMUConfig struct{}
func getHostUID() int {
return os.Getuid()
}

View File

@ -1,7 +1,13 @@
package vmconfigs
import "os"
// Stubs
type HyperVConfig struct{}
type WSLConfig struct {}
type QEMUConfig struct {}
type AppleHVConfig struct {}
type WSLConfig struct{}
type QEMUConfig struct{}
type AppleHVConfig struct{}
func getHostUID() int {
return os.Getuid()
}

View File

@ -1,14 +1,22 @@
package vmconfigs
import (
"os"
"github.com/containers/podman/v4/pkg/machine/qemu/command"
)
type QEMUConfig struct {
cmd command.QemuCmd //nolint:unused
Command command.QemuCmd
// QMPMonitor is the qemu monitor object for sending commands
QMPMonitor command.Monitor
}
// Stubs
type AppleHVConfig struct{}
type HyperVConfig struct{}
type WSLConfig struct{}
func getHostUID() int {
return os.Getuid()
}

View File

@ -19,3 +19,7 @@ type WSLConfig struct {
// Stubs
type QEMUConfig struct{}
type AppleHVConfig struct{}
func getHostUID() int {
return 1000
}

View File

@ -0,0 +1,200 @@
package vmconfigs
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/sirupsen/logrus"
define2 "github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/machine/lock"
"github.com/containers/podman/v4/utils"
)
/*
info Display machine host info common
init Initialize a virtual machine specific
inspect Inspect an existing machine specific
list List machines specific
os Manage a Podman virtual machine's OS common
rm Remove an existing machine specific
set Set a virtual machine setting specific
ssh SSH into an existing machine common
start Start an existing machine specific
stop Stop an existing machine specific
*/
var (
SSHRemoteConnection RemoteConnectionType = "ssh"
DefaultIgnitionUserName = "core"
ForwarderBinaryName = "gvproxy"
)
type RemoteConnectionType string
// NewMachineConfig creates the initial machine configuration file from cli options
func NewMachineConfig(opts define.InitOptions, machineConfigDir string) (*MachineConfig, error) {
mc := new(MachineConfig)
mc.Name = opts.Name
machineLock, err := lock.GetMachineLock(opts.Name, machineConfigDir)
if err != nil {
return nil, err
}
mc.lock = machineLock
cf, err := define.NewMachineFile(filepath.Join(machineConfigDir, fmt.Sprintf("%s.json", opts.Name)), nil)
if err != nil {
return nil, err
}
mc.configPath = cf
// System Resources
mrc := ResourceConfig{
CPUs: opts.CPUS,
DiskSize: opts.DiskSize,
Memory: opts.Memory,
USBs: nil, // Needs to be filled in by providers?
}
mc.Resources = mrc
sshPort, err := utils.GetRandomPort()
if err != nil {
return nil, err
}
// Single key examination should occur here
sshConfig := SSHConfig{
IdentityPath: "/home/baude/.local/share/containers/podman/machine", // TODO Fix this
Port: sshPort,
RemoteUsername: opts.Username,
}
mc.SSH = sshConfig
mc.Created = time.Now()
mc.HostUser = HostUser{UID: getHostUID(), Rootful: opts.Rootful}
// TODO - Temporarily disabled to make things easier
/*
// TODO AddSSHConnectionToPodmanSocket could put converted become a method of MachineConfig
if err := connection.AddSSHConnectionsToPodmanSocket(mc.HostUser.UID, mc.SSH.Port, mc.SSH.IdentityPath, mc.Name, mc.SSH.RemoteUsername, opts); err != nil {
return nil, err
}
*/
// addcallback for ssh connections here
return mc, nil
}
// Lock creates a lock on the machine for single access
func (mc *MachineConfig) Lock() {
mc.lock.Lock()
}
// Unlock removes an existing lock
func (mc *MachineConfig) Unlock() {
mc.lock.Unlock()
}
// Write is a locking way to the machine configuration file
func (mc *MachineConfig) Write() error {
mc.Lock()
defer mc.Unlock()
return mc.write()
}
// write is a non-locking way to write the machine configuration file to disk
func (mc *MachineConfig) write() error {
if mc.configPath == nil {
return fmt.Errorf("no configuration file associated with vm %q", mc.Name)
}
b, err := json.Marshal(mc)
if err != nil {
return err
}
logrus.Debugf("writing configuration file %q", mc.configPath.Path)
return os.WriteFile(mc.configPath.GetPath(), b, define.DefaultFilePerm)
}
func (mc *MachineConfig) removeSystemConnection() error { //nolint:unused
return define2.ErrNotImplemented
}
// updateLastBoot writes the current time to the machine configuration file. it is
// an non-locking method and assumes it is being called locked
func (mc *MachineConfig) updateLastBoot() error { //nolint:unused
mc.LastUp = time.Now()
return mc.Write()
}
func (mc *MachineConfig) removeMachineFiles() error { //nolint:unused
return define2.ErrNotImplemented
}
func (mc *MachineConfig) Info() error { // signature TBD
return define2.ErrNotImplemented
}
func (mc *MachineConfig) OSApply() error { // signature TBD
return define2.ErrNotImplemented
}
func (mc *MachineConfig) SecureShell() error { // Used SecureShell instead of SSH to do struct collision
return define2.ErrNotImplemented
}
func (mc *MachineConfig) Inspect() error { // signature TBD
return define2.ErrNotImplemented
}
func (mc *MachineConfig) ConfigDir() (string, error) {
if mc.configPath == nil {
return "", errors.New("no configuration directory set")
}
return filepath.Dir(mc.configPath.GetPath()), nil
}
// LoadMachineByName returns a machine config based on the vm name and provider
func LoadMachineByName(name, configDir string) (*MachineConfig, error) {
fullPath := filepath.Join(configDir, fmt.Sprintf("%s.json", name))
return loadMachineFromFQPath(fullPath)
}
// loadMachineFromFQPath stub function for loading a JSON configuration file and returning
// a machineconfig. this should only be called if you know what you are doing.
func loadMachineFromFQPath(path string) (*MachineConfig, error) {
mc := new(MachineConfig)
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
err = json.Unmarshal(b, mc)
return mc, err
}
// LoadMachinesInDir returns all the machineconfigs located in given dir
func LoadMachinesInDir(configDir string) (map[string]*MachineConfig, error) {
mcs := make(map[string]*MachineConfig)
if err := filepath.WalkDir(configDir, func(path string, d fs.DirEntry, err error) error {
if strings.HasSuffix(d.Name(), ".json") {
fullPath := filepath.Join(configDir, d.Name())
mc, err := loadMachineFromFQPath(fullPath)
if err != nil {
return err
}
mcs[mc.Name] = mc
}
return nil
}); err != nil {
return nil, err
}
return mcs, nil
}

View File

@ -27,7 +27,7 @@ func VirtualizationProvider() machine.VirtProvider {
}
// NewMachine initializes an instance of a wsl machine
func (p *WSLVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (p *WSLVirtualization) NewMachine(opts define.InitOptions) (machine.VM, error) {
vm := new(MachineVM)
if len(opts.USBs) > 0 {
return nil, fmt.Errorf("USB host passthrough is not supported for WSL machines")

View File

@ -16,6 +16,8 @@ import (
"strings"
"time"
"github.com/containers/podman/v4/pkg/machine/connection"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define"
@ -394,7 +396,7 @@ func getLegacyLastStart(vm *MachineVM) time.Time {
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
func (v *MachineVM) Init(opts define.InitOptions) (bool, error) {
var (
err error
)
@ -495,10 +497,10 @@ func (v *MachineVM) removeMachineImage() error {
}
func (v *MachineVM) removeSystemConnections() error {
return machine.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
return connection.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
}
func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
func downloadDistro(v *MachineVM, opts define.InitOptions) error {
var (
dd machine.DistributionDownload
err error
@ -525,8 +527,8 @@ func (v *MachineVM) writeConfig() error {
}
func constructSSHUris(v *MachineVM) ([]url.URL, []string) {
uri := machine.SSHRemoteConnection.MakeSSHURL(machine.LocalhostIP, rootlessSock, strconv.Itoa(v.Port), v.RemoteUsername)
uriRoot := machine.SSHRemoteConnection.MakeSSHURL(machine.LocalhostIP, rootfulSock, strconv.Itoa(v.Port), "root")
uri := connection.SSHRemoteConnection.MakeSSHURL(connection.LocalhostIP, rootlessSock, strconv.Itoa(v.Port), v.RemoteUsername)
uriRoot := connection.SSHRemoteConnection.MakeSSHURL(connection.LocalhostIP, rootfulSock, strconv.Itoa(v.Port), "root")
uris := []url.URL{uri, uriRoot}
names := []string{v.Name, v.Name + "-root"}
@ -534,7 +536,7 @@ func constructSSHUris(v *MachineVM) ([]url.URL, []string) {
return uris, names
}
func setupConnections(v *MachineVM, opts machine.InitOptions) error {
func setupConnections(v *MachineVM, opts define.InitOptions) error {
uris, names := constructSSHUris(v)
// The first connection defined when connections is empty will become the default
@ -552,7 +554,7 @@ func setupConnections(v *MachineVM, opts machine.InitOptions) error {
defer flock.unlock()
for i := 0; i < 2; i++ {
if err := machine.AddConnection(&uris[i], names[i], v.IdentityPath, opts.IsDefault && i == 0); err != nil {
if err := connection.AddConnection(&uris[i], names[i], v.IdentityPath, opts.IsDefault && i == 0); err != nil {
return err
}
}
@ -809,7 +811,7 @@ func writeWslConf(dist string, user string) error {
return nil
}
func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
func checkAndInstallWSL(opts define.InitOptions) (bool, error) {
if wutil.IsWSLInstalled() {
return true, nil
}
@ -844,7 +846,7 @@ func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
return true, nil
}
func attemptFeatureInstall(opts machine.InitOptions, admin bool) error {
func attemptFeatureInstall(opts define.InitOptions, admin bool) error {
if !winVersionAtLeast(10, 0, 18362) {
return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
} else if !winVersionAtLeast(10, 0, 19041) {
@ -1276,7 +1278,7 @@ func (v *MachineVM) reassignSshPort() error {
v.Port = newPort
uris, names := constructSSHUris(v)
for i := 0; i < 2; i++ {
if err := machine.ChangeConnectionURI(names[i], &uris[i]); err != nil {
if err := connection.ChangeConnectionURI(names[i], &uris[i]); err != nil {
return err
}
}
@ -1476,7 +1478,7 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
confirmationMessage += "\n"
return confirmationMessage, func() error {
if err := machine.RemoveConnections(v.Name, v.Name+"-root"); err != nil {
if err := connection.RemoveConnections(v.Name, v.Name+"-root"); err != nil {
logrus.Error(err)
}
if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil {