summaryrefslogtreecommitdiff
path: root/pkg/machine
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/machine')
-rw-r--r--pkg/machine/config.go9
-rw-r--r--pkg/machine/config_test.go71
-rw-r--r--pkg/machine/fcos.go1
-rw-r--r--pkg/machine/fedora.go6
-rw-r--r--pkg/machine/ignition.go5
-rw-r--r--pkg/machine/ignition_darwin.go3
-rw-r--r--pkg/machine/ignition_schema.go1
-rw-r--r--pkg/machine/ignition_windows.go3
-rw-r--r--pkg/machine/keys.go1
-rw-r--r--pkg/machine/machine_unsupported.go1
-rw-r--r--pkg/machine/pull.go7
-rw-r--r--pkg/machine/qemu/config.go169
-rw-r--r--pkg/machine/qemu/config_test.go133
-rw-r--r--pkg/machine/qemu/machine.go575
-rw-r--r--pkg/machine/qemu/machine_unsupported.go1
-rw-r--r--pkg/machine/qemu/options_darwin_arm64.go6
-rw-r--r--pkg/machine/wsl/machine.go37
-rw-r--r--pkg/machine/wsl/machine_unsupported.go1
18 files changed, 836 insertions, 194 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index b3b105150..7e1561506 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -29,16 +29,16 @@ type InitOptions struct {
Username string
ReExec bool
Rootful bool
- // The numberical userid of the user that called machine
+ // The numerical userid of the user that called machine
UID string
}
type QemuMachineStatus = string
const (
- // Running indicates the qemu vm is running
+ // Running indicates the qemu vm is running.
Running QemuMachineStatus = "running"
- // Stopped indicates the vm has stopped
+ // Stopped indicates the vm has stopped.
Stopped QemuMachineStatus = "stopped"
DefaultMachineName string = "podman-machine-default"
)
@@ -128,6 +128,7 @@ type DistributionDownload interface {
}
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",
@@ -147,7 +148,7 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url
}
// GetDataDir returns the filepath where vm images should
-// live for podman-machine
+// live for podman-machine.
func GetDataDir(vmType string) (string, error) {
data, err := homedir.GetDataHome()
if err != nil {
diff --git a/pkg/machine/config_test.go b/pkg/machine/config_test.go
new file mode 100644
index 000000000..d9fc5425e
--- /dev/null
+++ b/pkg/machine/config_test.go
@@ -0,0 +1,71 @@
+package machine
+
+import (
+ "net"
+ "net/url"
+ "reflect"
+ "testing"
+)
+
+func TestRemoteConnectionType_MakeSSHURL(t *testing.T) {
+ var (
+ host = "foobar"
+ path = "/path/to/socket"
+ rc = "ssh"
+ username = "core"
+ )
+ type args struct {
+ host string
+ path string
+ port string
+ userName string
+ }
+ tests := []struct {
+ name string
+ rc RemoteConnectionType
+ args args
+ want url.URL
+ }{
+ {
+ name: "Good no port",
+ rc: "ssh",
+ args: args{
+ host: host,
+ path: path,
+ port: "",
+ userName: username,
+ },
+ want: url.URL{
+ Scheme: rc,
+ User: url.User(username),
+ Host: host,
+ Path: path,
+ ForceQuery: false,
+ },
+ },
+ {
+ name: "Good with port",
+ rc: "ssh",
+ args: args{
+ host: host,
+ path: path,
+ port: "222",
+ userName: username,
+ },
+ want: url.URL{
+ Scheme: rc,
+ User: url.User(username),
+ Host: net.JoinHostPort(host, "222"),
+ Path: path,
+ ForceQuery: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := tt.rc.MakeSSHURL(tt.args.host, tt.args.path, tt.args.port, tt.args.userName); !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
+ t.Errorf("MakeSSHURL() = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go
index 4d3e2edf4..6215ae08f 100644
--- a/pkg/machine/fcos.go
+++ b/pkg/machine/fcos.go
@@ -1,3 +1,4 @@
+//go:build amd64 || arm64
// +build amd64 arm64
package machine
diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go
index cd713dde7..bed45c6da 100644
--- a/pkg/machine/fedora.go
+++ b/pkg/machine/fedora.go
@@ -1,3 +1,4 @@
+//go:build amd64 || arm64
// +build amd64 arm64
package machine
@@ -58,7 +59,10 @@ func (f FedoraDownload) Get() *Download {
func (f FedoraDownload) HasUsableCache() (bool, error) {
info, err := os.Stat(f.LocalPath)
if err != nil {
- return false, nil
+ if errors.Is(err, os.ErrNotExist) {
+ return false, nil
+ }
+ return false, err
}
return info.Size() == f.Size, nil
}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index b2dabb689..fe47437e3 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -6,6 +6,7 @@ package machine
import (
"encoding/json"
"fmt"
+ "io/fs"
"io/ioutil"
"net/url"
"os"
@@ -507,8 +508,8 @@ func getCerts(certsDir string, isDir bool) []File {
)
if isDir {
- err := filepath.Walk(certsDir, func(path string, info os.FileInfo, err error) error {
- if err == nil && !info.IsDir() {
+ err := filepath.WalkDir(certsDir, func(path string, d fs.DirEntry, err error) error {
+ if err == nil && !d.IsDir() {
certPath, err := filepath.Rel(certsDir, path)
if err != nil {
logrus.Warnf("%s", err)
diff --git a/pkg/machine/ignition_darwin.go b/pkg/machine/ignition_darwin.go
index 9ede4b026..b9fbf218d 100644
--- a/pkg/machine/ignition_darwin.go
+++ b/pkg/machine/ignition_darwin.go
@@ -1,4 +1,5 @@
-//+build darwin
+//go:build darwin
+// +build darwin
package machine
diff --git a/pkg/machine/ignition_schema.go b/pkg/machine/ignition_schema.go
index 8cfb0d04e..d6b86229c 100644
--- a/pkg/machine/ignition_schema.go
+++ b/pkg/machine/ignition_schema.go
@@ -1,3 +1,4 @@
+//go:build amd64 || arm64
// +build amd64 arm64
package machine
diff --git a/pkg/machine/ignition_windows.go b/pkg/machine/ignition_windows.go
index c0de48bd3..0fcc06273 100644
--- a/pkg/machine/ignition_windows.go
+++ b/pkg/machine/ignition_windows.go
@@ -1,4 +1,5 @@
-//+build windows
+//go:build windows
+// +build windows
package machine
diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go
index 711b091f0..15c1f73d8 100644
--- a/pkg/machine/keys.go
+++ b/pkg/machine/keys.go
@@ -1,3 +1,4 @@
+//go:build amd64 || arm64
// +build amd64 arm64
package machine
diff --git a/pkg/machine/machine_unsupported.go b/pkg/machine/machine_unsupported.go
index da1437984..a12140e16 100644
--- a/pkg/machine/machine_unsupported.go
+++ b/pkg/machine/machine_unsupported.go
@@ -1,3 +1,4 @@
+//go:build !amd64 && !arm64
// +build !amd64,!arm64
package machine
diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go
index 280b47f96..7e6f01bad 100644
--- a/pkg/machine/pull.go
+++ b/pkg/machine/pull.go
@@ -1,3 +1,4 @@
+//go:build amd64 || arm64
// +build amd64 arm64
package machine
@@ -19,8 +20,8 @@ import (
"github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
"github.com/ulikunitz/xz"
- "github.com/vbauerster/mpb/v6"
- "github.com/vbauerster/mpb/v6/decor"
+ "github.com/vbauerster/mpb/v7"
+ "github.com/vbauerster/mpb/v7/decor"
)
// GenericDownload is used when a user provides a URL
@@ -128,7 +129,7 @@ func DownloadVMImage(downloadURL *url2.URL, localImagePath string) error {
}()
if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status)
+ return fmt.Errorf("downloading VM image %s: %s", downloadURL, resp.Status)
}
size := resp.ContentLength
urlSplit := strings.Split(downloadURL.Path, "/")
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index b39334be0..4d4e3a6c1 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -4,12 +4,33 @@
package qemu
import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"time"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // FCOS streams
+ // Testing FCOS stream
+ Testing string = "testing"
+ // Next FCOS stream
+ Next string = "next"
+ // Stable FCOS stream
+ Stable string = "stable"
+
+ // Max length of fully qualified socket path
+ maxSocketPathLength int = 103
)
type Provider struct{}
-type MachineVM struct {
+// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
+// structure
+type MachineVMV1 struct {
// CPUs to be assigned to the VM
CPUs uint64
// The command line representation of the qemu command
@@ -33,7 +54,7 @@ type MachineVM struct {
// SSH port for user networking
Port int
// QMPMonitor is the qemu monitor object for sending commands
- QMPMonitor Monitor
+ QMPMonitor Monitorv1
// RemoteUsername of the vm user
RemoteUsername string
// Whether this machine should run in a rootful or rootless manner
@@ -42,6 +63,76 @@ type MachineVM struct {
UID int
}
+type MachineVM struct {
+ // ConfigPath is the path to the configuration file
+ ConfigPath MachineFile
+ // The command line representation of the qemu command
+ CmdLine []string
+ // HostUser contains info about host user
+ HostUser
+ // ImageConfig describes the bootable image
+ ImageConfig
+ // Mounts is the list of remote filesystems to mount
+ Mounts []Mount
+ // Name of VM
+ Name string
+ // PidFilePath is the where the PID file lives
+ PidFilePath MachineFile
+ // QMPMonitor is the qemu monitor object for sending commands
+ QMPMonitor Monitor
+ // ReadySocket tells host when vm is booted
+ ReadySocket MachineFile
+ // ResourceConfig is physical attrs of the VM
+ ResourceConfig
+ // SSHConfig for accessing the remote vm
+ SSHConfig
+}
+
+// ImageConfig describes the bootable image for the VM
+type ImageConfig struct {
+ IgnitionFilePath MachineFile
+ // ImageStream is the update stream for the image
+ ImageStream string
+ // ImagePath is the fq path to
+ ImagePath MachineFile
+}
+
+// HostUser describes the host user
+type HostUser struct {
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
+ // UID is the numerical id of the user that called machine
+ UID int
+}
+
+// SSHConfig contains remote access information for SSH
+type SSHConfig struct {
+ // IdentityPath is the fq path to the ssh priv key
+ IdentityPath string
+ // SSH port for user networking
+ Port int
+ // RemoteUsername of the vm user
+ RemoteUsername string
+}
+
+// ResourceConfig describes physical attributes of the machine
+type ResourceConfig struct {
+ // CPUs to be assigned to the VM
+ CPUs uint64
+ // Memory in megabytes assigned to the vm
+ Memory uint64
+ // Disk size in gigabytes assigned to the vm
+ DiskSize uint64
+}
+
+type MachineFile struct {
+ // Path is the fully qualified path to a file
+ Path string
+ // Symlink is a shortened version of Path by using
+ // a symlink
+ Symlink *string
+}
+
type Mount struct {
Type string
Tag string
@@ -50,7 +141,7 @@ type Mount struct {
ReadOnly bool
}
-type Monitor struct {
+type Monitorv1 struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
Address string
// Network portion of the qmp monitor (unix)
@@ -59,8 +150,78 @@ type Monitor struct {
Timeout time.Duration
}
+type Monitor struct {
+ // Address portion of the qmp monitor (/tmp/tmp.sock)
+ Address MachineFile
+ // Network portion of the qmp monitor (unix)
+ Network string
+ // Timeout in seconds for qmp monitor transactions
+ Timeout time.Duration
+}
+
var (
// defaultQMPTimeout is the timeout duration for the
- // qmp monitor interactions
+ // qmp monitor interactions.
defaultQMPTimeout time.Duration = 2 * time.Second
)
+
+// GetPath returns the working path for a machinefile. it returns
+// the symlink unless one does not exist
+func (m *MachineFile) GetPath() string {
+ if m.Symlink == nil {
+ return m.Path
+ }
+ return *m.Symlink
+}
+
+// Delete removes the machinefile symlink (if it exists) and
+// the actual path
+func (m *MachineFile) Delete() error {
+ if m.Symlink != nil {
+ if err := os.Remove(*m.Symlink); err != nil && !errors.Is(err, os.ErrNotExist) {
+ logrus.Errorf("unable to remove symlink %q", *m.Symlink)
+ }
+ }
+ if err := os.Remove(m.Path); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ return nil
+}
+
+// Read the contents of a given file and return in []bytes
+func (m *MachineFile) Read() ([]byte, error) {
+ return ioutil.ReadFile(m.GetPath())
+}
+
+// NewMachineFile is a constructor for MachineFile
+func NewMachineFile(path string, symlink *string) (*MachineFile, error) {
+ if len(path) < 1 {
+ return nil, errors.New("invalid machine file path")
+ }
+ if symlink != nil && len(*symlink) < 1 {
+ return nil, errors.New("invalid symlink path")
+ }
+ mf := MachineFile{Path: path}
+ if symlink != nil && len(path) > maxSocketPathLength {
+ if err := mf.makeSymlink(symlink); err != nil && !errors.Is(err, os.ErrExist) {
+ return nil, err
+ }
+ }
+ return &mf, nil
+}
+
+// makeSymlink for macOS creates a symlink in $HOME/.podman/
+// for a machinefile like a socket
+func (m *MachineFile) makeSymlink(symlink *string) error {
+ homedir, err := os.UserHomeDir()
+ if err != nil {
+ return err
+ }
+ sl := filepath.Join(homedir, ".podman", *symlink)
+ // make the symlink dir and throw away if it already exists
+ if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ m.Symlink = &sl
+ return os.Symlink(m.Path, sl)
+}
diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go
new file mode 100644
index 000000000..264de9ae8
--- /dev/null
+++ b/pkg/machine/qemu/config_test.go
@@ -0,0 +1,133 @@
+package qemu
+
+import (
+ "os"
+ "path/filepath"
+ "reflect"
+ "testing"
+
+ "github.com/containers/podman/v4/test/utils"
+)
+
+func TestMachineFile_GetPath(t *testing.T) {
+ path := "/var/tmp/podman/my.sock"
+ sym := "/tmp/podman/my.sock"
+ type fields struct {
+ Path string
+ Symlink *string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "Original path",
+ fields: fields{path, nil},
+ want: path,
+ },
+ {
+ name: "Symlink over path",
+ fields: fields{
+ Path: path,
+ Symlink: &sym,
+ },
+ want: sym,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := &MachineFile{
+ Path: tt.fields.Path, //nolint: scopelint
+ Symlink: tt.fields.Symlink, //nolint: scopelint
+ }
+ if got := m.GetPath(); got != tt.want { //nolint: scopelint
+ t.Errorf("GetPath() = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
+
+func TestNewMachineFile(t *testing.T) {
+ empty := ""
+
+ homedir, err := os.MkdirTemp("/tmp", "homedir")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(homedir)
+ longTemp, err := os.MkdirTemp("/tmp", "tmpdir")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(longTemp)
+ oldhome := os.Getenv("HOME")
+ os.Setenv("HOME", homedir) //nolint: tenv
+ defer os.Setenv("HOME", oldhome)
+
+ p := "/var/tmp/podman/my.sock"
+ longp := filepath.Join(longTemp, utils.RandomString(100), "my.sock")
+ os.MkdirAll(filepath.Dir(longp), 0755)
+ f, _ := os.Create(longp)
+ f.Close()
+ sym := "my.sock"
+ longSym := filepath.Join(homedir, ".podman", sym)
+
+ m := MachineFile{
+ Path: p,
+ Symlink: nil,
+ }
+ type args struct {
+ path string
+ symlink *string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *MachineFile
+ wantErr bool
+ }{
+ {
+ name: "Good",
+ args: args{path: p},
+ want: &m,
+ wantErr: false,
+ },
+ {
+ name: "Good with short symlink",
+ args: args{p, &sym},
+ want: &MachineFile{p, nil},
+ wantErr: false,
+ },
+ {
+ name: "Bad path name",
+ args: args{empty, nil},
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "Bad symlink name",
+ args: args{p, &empty},
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "Good with long symlink",
+ args: args{longp, &sym},
+ want: &MachineFile{longp, &longSym},
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := NewMachineFile(tt.args.path, tt.args.symlink) //nolint: scopelint
+ if (err != nil) != tt.wantErr { //nolint: scopelint
+ t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) //nolint: scopelint
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
+ t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) //nolint: scopelint
+ }
+ })
+ }
+}
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 43a79dae8..e849bae3f 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -34,7 +34,7 @@ import (
var (
qemuProvider = &Provider{}
- // vmtype refers to qemu (vs libvirt, krun, etc)
+ // vmtype refers to qemu (vs libvirt, krun, etc).
vmtype = "qemu"
)
@@ -71,10 +71,17 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
if len(opts.Name) > 0 {
vm.Name = opts.Name
}
- ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
- vm.IgnitionFilePath = ignitionFile
+ ignitionFile, err := NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil)
+ if err != nil {
+ return nil, err
+ }
+ vm.IgnitionFilePath = *ignitionFile
- vm.ImagePath = opts.ImagePath
+ imagePath, err := NewMachineFile(opts.ImagePath, nil)
+ if err != nil {
+ return nil, err
+ }
+ vm.ImagePath = *imagePath
vm.RemoteUsername = opts.Username
// Add a random port for ssh
@@ -98,55 +105,133 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return nil, err
}
- cmd := append([]string{execPath})
+ cmd := []string{execPath}
// Add memory
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
// Add cpus
cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...)
// Add ignition file
- cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath}...)
+ cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath.GetPath()}...)
// Add qmp socket
monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
if err != nil {
return nil, err
}
vm.QMPMonitor = monitor
- cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...)
+ cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
// Add network
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
// why we can only run one vm at a time right now
cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
- socketPath, err := getRuntimeDir()
- if err != nil {
+ if err := vm.setReadySocket(); err != nil {
return nil, err
}
- virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock")
+
// Add serial port for readiness
cmd = append(cmd, []string{
"-device", "virtio-serial",
- "-chardev", "socket,path=" + virtualSocketPath + ",server=on,wait=off,id=" + vm.Name + "_ready",
+ // qemu needs to establish the long name; other connections can use the symlink'd
+ "-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready",
"-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...)
vm.CmdLine = cmd
+ if err := vm.setPIDSocket(); err != nil {
+ return nil, err
+ }
return vm, nil
}
-// LoadByName reads a json file that describes a known qemu vm
-// and returns a vm instance
-func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
- vm := new(MachineVM)
- vmConfigDir, err := machine.GetConfDir(vmtype)
+// migrateVM takes the old configuration structure and migrates it
+// to the new structure and writes it to the filesystem
+func migrateVM(configPath string, config []byte, vm *MachineVM) error {
+ fmt.Printf("Migrating machine %q\n", vm.Name)
+ var old MachineVMV1
+ err := json.Unmarshal(config, &old)
if err != nil {
- return nil, err
+ return err
+ }
+ // Looks like we loaded the older structure; now we need to migrate
+ // from the old structure to the new structure
+ _, pidFile, err := vm.getSocketandPid()
+ if err != nil {
+ return err
}
- b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
- if os.IsNotExist(err) {
- return nil, errors.Wrap(machine.ErrNoSuchVM, name)
+
+ pidFilePath := MachineFile{Path: pidFile}
+ qmpMonitor := Monitor{
+ Address: MachineFile{Path: old.QMPMonitor.Address},
+ Network: old.QMPMonitor.Network,
+ Timeout: old.QMPMonitor.Timeout,
}
+ socketPath, err := getRuntimeDir()
if err != nil {
+ return err
+ }
+ virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock")
+ readySocket := MachineFile{Path: virtualSocketPath}
+
+ vm.HostUser = HostUser{}
+ vm.ImageConfig = ImageConfig{}
+ vm.ResourceConfig = ResourceConfig{}
+ vm.SSHConfig = SSHConfig{}
+
+ ignitionFilePath, err := NewMachineFile(old.IgnitionFilePath, nil)
+ if err != nil {
+ return err
+ }
+ imagePath, err := NewMachineFile(old.ImagePath, nil)
+ if err != nil {
+ return err
+ }
+
+ // setReadySocket will stick the entry into the new struct
+ if err := vm.setReadySocket(); err != nil {
+ return err
+ }
+
+ vm.CPUs = old.CPUs
+ vm.CmdLine = old.CmdLine
+ vm.DiskSize = old.DiskSize
+ vm.IdentityPath = old.IdentityPath
+ vm.IgnitionFilePath = *ignitionFilePath
+ vm.ImagePath = *imagePath
+ vm.ImageStream = old.ImageStream
+ vm.Memory = old.Memory
+ vm.Mounts = old.Mounts
+ vm.Name = old.Name
+ vm.PidFilePath = pidFilePath
+ vm.Port = old.Port
+ vm.QMPMonitor = qmpMonitor
+ vm.ReadySocket = readySocket
+ vm.RemoteUsername = old.RemoteUsername
+ vm.Rootful = old.Rootful
+ vm.UID = old.UID
+
+ // Backup the original config file
+ if err := os.Rename(configPath, configPath+".orig"); err != nil {
+ return err
+ }
+ // Write the config file
+ if err := vm.writeConfig(); err != nil {
+ // If the config file fails to be written, put the origina
+ // config file back before erroring
+ if renameError := os.Rename(configPath+".orig", configPath); renameError != nil {
+ logrus.Warn(renameError)
+ }
+ return err
+ }
+ // Remove the backup file
+ return os.Remove(configPath + ".orig")
+}
+
+// LoadVMByName reads a json file that describes a known qemu vm
+// and returns a vm instance
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
+ vm := &MachineVM{Name: name}
+ vm.HostUser = HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
+ if err := vm.update(); err != nil {
return nil, err
}
- err = json.Unmarshal(b, vm)
// It is here for providing the ability to propagate
// proxy settings (e.g. HTTP_PROXY and others) on a start
@@ -162,7 +247,7 @@ func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
}
logrus.Debug(vm.CmdLine)
- return vm, err
+ return vm, nil
}
// Init writes the json configuration file to the filesystem for
@@ -176,7 +261,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
v.Rootful = opts.Rootful
switch opts.ImagePath {
- case "testing", "next", "stable", "":
+ case Testing, Next, Stable, "":
// Get image as usual
v.ImageStream = opts.ImagePath
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
@@ -184,7 +269,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil {
return false, err
}
- v.ImagePath = dd.Get().LocalUncompressedFile
+ uncompressedFile, err := NewMachineFile(dd.Get().LocalUncompressedFile, nil)
+ if err != nil {
+ return false, err
+ }
+ v.ImagePath = *uncompressedFile
if err := machine.DownloadImage(dd); err != nil {
return false, err
}
@@ -196,14 +285,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil {
return false, err
}
- v.ImagePath = g.Get().LocalUncompressedFile
+ imagePath, err := NewMachineFile(g.Get().LocalUncompressedFile, nil)
+ if err != nil {
+ return false, err
+ }
+ v.ImagePath = *imagePath
if err := machine.DownloadImage(g); err != nil {
return false, err
}
}
// Add arch specific options including image location
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
-
var volumeType string
switch opts.VolumeDriver {
case "virtfs":
@@ -253,7 +345,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
v.UID = os.Getuid()
// Add location of bootable image
- v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath)
+ v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.getImageFile())
// This kind of stinks but no other way around this r/n
if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID), strconv.Itoa(v.Port), v.RemoteUsername)
@@ -278,7 +370,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
}
// Write the JSON file
- v.writeConfig()
+ if err := v.writeConfig(); err != nil {
+ return false, fmt.Errorf("writing JSON file: %w", err)
+ }
// User has provided ignition file so keygen
// will be skipped.
@@ -294,7 +388,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return false, err
}
- originalDiskSize, err := getDiskSize(v.ImagePath)
+ originalDiskSize, err := getDiskSize(v.getImageFile())
if err != nil {
return false, err
}
@@ -311,11 +405,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil {
return false, err
}
- resize := exec.Command(resizePath, []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...)
+ resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(opts.DiskSize)) + "G"}...)
resize.Stdout = os.Stdout
resize.Stderr = os.Stderr
if err := resize.Run(); err != nil {
- return false, errors.Errorf("error resizing image: %q", err)
+ return false, errors.Errorf("resizing image: %q", err)
}
}
// If the user provides an ignition file, we need to
@@ -325,7 +419,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil {
return false, err
}
- return false, ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
+ return false, ioutil.WriteFile(v.getIgnitionFile(), inputIgnition, 0644)
}
// Write the ignition file
ign := machine.DynamicIgnition{
@@ -333,18 +427,31 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
Key: key,
VMName: v.Name,
TimeZone: opts.TimeZone,
- WritePath: v.IgnitionFilePath,
+ WritePath: v.getIgnitionFile(),
UID: v.UID,
}
err = machine.NewIgnitionFile(ign)
return err == nil, err
}
-func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
+func (v *MachineVM) Set(_ string, opts machine.SetOptions) error {
if v.Rootful == opts.Rootful {
return nil
}
+ running, err := v.isRunning()
+ if err != nil {
+ return err
+ }
+
+ if running {
+ suffix := ""
+ if v.Name != machine.DefaultMachineName {
+ suffix = " " + v.Name
+ }
+ return errors.Errorf("cannot change setting while the vm is running, run 'podman machine stop%s' first", suffix)
+ }
+
changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
if err != nil {
return err
@@ -370,9 +477,13 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
conn net.Conn
err error
qemuSocketConn net.Conn
- wait time.Duration = time.Millisecond * 500
+ wait = time.Millisecond * 500
)
+ if v.isIncompatible() {
+ logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name)
+ }
+
forwardSock, forwardState, err := v.startHostNetworking()
if err != nil {
return errors.Errorf("unable to start host networking: %q", err)
@@ -390,17 +501,13 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return err
}
}
- qemuSocketPath, _, err := v.getSocketandPid()
- if err != nil {
- return err
- }
// If the qemusocketpath exists and the vm is off/down, we should rm
// it before the dial as to avoid a segv
- if err := os.Remove(qemuSocketPath); err != nil && !errors.Is(err, os.ErrNotExist) {
- logrus.Warn(err)
+ if err := v.QMPMonitor.Address.Delete(); err != nil {
+ return err
}
for i := 0; i < 6; i++ {
- qemuSocketConn, err = net.Dial("unix", qemuSocketPath)
+ qemuSocketConn, err = net.Dial("unix", v.QMPMonitor.Address.GetPath())
if err == nil {
break
}
@@ -424,13 +531,29 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
// Disable graphic window when not in debug mode
// Done in start, so we're not suck with the debug level we used on init
- if logrus.GetLevel() != logrus.DebugLevel {
+ if !logrus.IsLevelEnabled(logrus.DebugLevel) {
cmd = append(cmd, "-display", "none")
}
_, err = os.StartProcess(v.CmdLine[0], cmd, attr)
if err != nil {
- return err
+ // check if qemu was not found
+ if !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
+ cfg, err := config.Default()
+ if err != nil {
+ return err
+ }
+ cmd[0], err = cfg.FindHelperBinary(QemuCommand, true)
+ if err != nil {
+ return err
+ }
+ _, err = os.StartProcess(cmd[0], cmd, attr)
+ if err != nil {
+ return err
+ }
}
fmt.Println("Waiting for VM ...")
socketPath, err := getRuntimeDir()
@@ -506,7 +629,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
}
}
- waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name)
+ v.waitAPIAndPrintInfo(forwardState, forwardSock)
return nil
}
@@ -551,15 +674,15 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachine
}
// Stop uses the qmp monitor to call a system_powerdown
-func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
+func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
var disconnected bool
// check if the qmp socket is there. if not, qemu instance is gone
- if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
+ if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
// Right now it is NOT an error to stop a stopped machine
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
return nil
}
- qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
+ qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
return err
}
@@ -588,14 +711,10 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return err
}
- qemuSocketFile, pidFile, err := v.getSocketandPid()
- if err != nil {
- return err
- }
- if _, err := os.Stat(pidFile); os.IsNotExist(err) {
+ if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) {
return nil
}
- pidString, err := ioutil.ReadFile(pidFile)
+ pidString, err := v.PidFilePath.Read()
if err != nil {
return err
}
@@ -613,16 +732,17 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return err
}
// Remove the pidfile
- if err := os.Remove(pidFile); err != nil && !errors.Is(err, os.ErrNotExist) {
- logrus.Warn(err)
+ if err := v.PidFilePath.Delete(); err != nil {
+ return err
}
// Remove socket
- if err := os.Remove(qemuSocketFile); err != nil {
+ if err := v.QMPMonitor.Address.Delete(); err != nil {
return err
}
if err := qmpMonitor.Disconnect(); err != nil {
- return nil
+ // FIXME: this error should probably be returned
+ return nil // nolint: nilerr
}
disconnected = true
@@ -639,7 +759,7 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
waitInternal = waitInternal * 2
}
- return nil
+ return v.ReadySocket.Delete()
}
// NewQMPMonitor creates the monitor subsection of our vm
@@ -661,25 +781,30 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error)
if timeout == 0 {
timeout = defaultQMPTimeout
}
+ address, err := NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil)
+ if err != nil {
+ return Monitor{}, err
+ }
monitor := Monitor{
Network: network,
- Address: filepath.Join(rtDir, "qmp_"+name+".sock"),
+ Address: *address,
Timeout: timeout,
}
return monitor, nil
}
-func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
+// Remove deletes all the files associated with a machine including ssh keys, the image itself
+func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) {
var (
files []string
)
- // cannot remove a running vm
+ // cannot remove a running vm unless --force is used
running, err := v.isRunning()
if err != nil {
return "", nil, err
}
- if running {
+ if running && !opts.Force {
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
}
@@ -688,16 +813,19 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
files = append(files, v.IdentityPath, v.IdentityPath+".pub")
}
if !opts.SaveIgnition {
- files = append(files, v.IgnitionFilePath)
+ files = append(files, v.getIgnitionFile())
}
if !opts.SaveImage {
- files = append(files, v.ImagePath)
+ files = append(files, v.getImageFile())
}
- socketPath, err := v.getForwardSocketPath()
+ socketPath, err := v.forwardSocketPath()
if err != nil {
- logrus.Error(err)
+ return "", nil, err
+ }
+ if socketPath.Symlink != nil {
+ files = append(files, *socketPath.Symlink)
}
- files = append(files, socketPath)
+ files = append(files, socketPath.Path)
files = append(files, v.archRemovalFiles()...)
if err := machine.RemoveConnection(v.Name); err != nil {
@@ -717,19 +845,14 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
confirmationMessage += msg + "\n"
}
- // Get path to socket and pidFile before we do any cleanups
- qemuSocketFile, pidFile, errSocketFile := v.getSocketandPid()
- //silently try to delete socket and pid file
//remove socket and pid file if any: warn at low priority if things fail
- if errSocketFile == nil {
- // Remove the pidfile
- if err := os.Remove(pidFile); err != nil && !errors.Is(err, os.ErrNotExist) {
- logrus.Debugf("Error while removing pidfile: %v", err)
- }
- // Remove socket
- if err := os.Remove(qemuSocketFile); err != nil && !errors.Is(err, os.ErrNotExist) {
- logrus.Debugf("Error while removing podman-machine-socket: %v", err)
- }
+ // Remove the pidfile
+ if err := v.PidFilePath.Delete(); err != nil {
+ logrus.Debugf("Error while removing pidfile: %v", err)
+ }
+ // Remove socket
+ if err := v.QMPMonitor.Address.Delete(); err != nil {
+ logrus.Debugf("Error while removing podman-machine-socket: %v", err)
}
confirmationMessage += "\n"
@@ -745,13 +868,14 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
func (v *MachineVM) isRunning() (bool, error) {
// Check if qmp socket path exists
- if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
+ if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
return false, nil
}
// Check if we can dial it
- monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
+ monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
if err != nil {
- return false, nil
+ // FIXME: this error should probably be returned
+ return false, nil // nolint: nilerr
}
if err := monitor.Connect(); err != nil {
return false, err
@@ -774,7 +898,7 @@ func (v *MachineVM) isRunning() (bool, error) {
func (v *MachineVM) isListening() bool {
// Check if we can dial it
- conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond)
+ conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond)
if err != nil {
return false
}
@@ -784,7 +908,7 @@ func (v *MachineVM) isListening() bool {
// SSH opens an interactive SSH session to the vm specified.
// Added ssh function to VM interface: pkg/machine/config/go : line 58
-func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
+func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
running, err := v.isRunning()
if err != nil {
return err
@@ -860,10 +984,10 @@ func getDiskSize(path string) (uint64, error) {
// List lists all vm's that use qemu virtualization
func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
- return GetVMInfos()
+ return getVMInfos()
}
-func GetVMInfos() ([]*machine.ListResponse, error) {
+func getVMInfos() ([]*machine.ListResponse, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
@@ -871,17 +995,22 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse
- if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
vm := new(MachineVM)
- if strings.HasSuffix(info.Name(), ".json") {
- fullPath := filepath.Join(vmConfigDir, info.Name())
+ if strings.HasSuffix(d.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, d.Name())
b, err := ioutil.ReadFile(fullPath)
if err != nil {
return err
}
err = json.Unmarshal(b, vm)
if err != nil {
- return err
+ // Checking if the file did not unmarshal because it is using
+ // the deprecated config file format.
+ migrateErr := migrateVM(fullPath, b, vm)
+ if migrateErr != nil {
+ return migrateErr
+ }
}
listEntry := new(machine.ListResponse)
@@ -900,7 +1029,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
}
listEntry.CreatedAt = fi.ModTime()
- fi, err = os.Stat(vm.ImagePath)
+ fi, err = os.Stat(vm.getImageFile())
if err != nil {
return err
}
@@ -923,7 +1052,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
}
func (p *Provider) IsValidVMName(name string) (bool, error) {
- infos, err := GetVMInfos()
+ infos, err := getVMInfos()
if err != nil {
return false, err
}
@@ -938,7 +1067,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
// CheckExclusiveActiveVM checks if there is a VM already running
// that does not allow other VMs to be running
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
- vms, err := GetVMInfos()
+ vms, err := getVMInfos()
if err != nil {
return false, "", errors.Wrap(err, "error checking VM active")
}
@@ -962,20 +1091,20 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
return "", noForwarding, err
}
- qemuSocket, pidFile, err := v.getSocketandPid()
- if err != nil {
- return "", noForwarding, err
- }
attr := new(os.ProcAttr)
// Pass on stdin, stdout, stderr
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
attr.Files = files
cmd := []string{binary}
- cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...)
+ cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...)
// Add the ssh port
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
- cmd, forwardSock, state := v.setupAPIForwarding(cmd)
+ var forwardSock string
+ var state apiForwardingState
+ if !v.isIncompatible() {
+ cmd, forwardSock, state = v.setupAPIForwarding(cmd)
+ }
if logrus.GetLevel() == logrus.DebugLevel {
cmd = append(cmd, "--debug")
@@ -986,7 +1115,7 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
}
func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {
- socket, err := v.getForwardSocketPath()
+ socket, err := v.forwardSocketPath()
if err != nil {
return cmd, "", noForwarding
@@ -1000,58 +1129,126 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa
forwardUser = "root"
}
- cmd = append(cmd, []string{"-forward-sock", socket}...)
+ cmd = append(cmd, []string{"-forward-sock", socket.GetPath()}...)
cmd = append(cmd, []string{"-forward-dest", destSock}...)
cmd = append(cmd, []string{"-forward-user", forwardUser}...)
cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
- link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock")
// The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
// This allows the helper to only have to maintain one constant target to the user, which can be
// repositioned without updating docker.sock.
+
+ link, err := v.userGlobalSocketLink()
+ if err != nil {
+ return cmd, socket.GetPath(), machineLocal
+ }
+
if !dockerClaimSupported() {
- return cmd, socket, claimUnsupported
+ return cmd, socket.GetPath(), claimUnsupported
}
if !dockerClaimHelperInstalled() {
- return cmd, socket, notInstalled
+ return cmd, socket.GetPath(), notInstalled
}
- if !alreadyLinked(socket, link) {
+ if !alreadyLinked(socket.GetPath(), link) {
if checkSockInUse(link) {
- return cmd, socket, machineLocal
+ return cmd, socket.GetPath(), machineLocal
}
_ = os.Remove(link)
- if err = os.Symlink(socket, link); err != nil {
+ if err = os.Symlink(socket.GetPath(), link); err != nil {
logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
- return cmd, socket, machineLocal
+ return cmd, socket.GetPath(), machineLocal
}
}
if !alreadyLinked(link, dockerSock) {
if checkSockInUse(dockerSock) {
- return cmd, socket, machineLocal
+ return cmd, socket.GetPath(), machineLocal
}
if !claimDockerSock() {
logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
- return cmd, socket, machineLocal
+ return cmd, socket.GetPath(), machineLocal
}
}
return cmd, dockerSock, dockerGlobal
}
-func (v *MachineVM) getForwardSocketPath() (string, error) {
+func (v *MachineVM) isIncompatible() bool {
+ return v.UID == -1
+}
+
+func (v *MachineVM) userGlobalSocketLink() (string, error) {
+ path, err := machine.GetDataDir(v.Name)
+ if err != nil {
+ logrus.Errorf("Resolving data dir: %s", err.Error())
+ return "", err
+ }
+ // User global socket is located in parent directory of machine dirs (one per user)
+ return filepath.Join(filepath.Dir(path), "podman.sock"), err
+}
+
+func (v *MachineVM) forwardSocketPath() (*MachineFile, error) {
+ sockName := "podman.sock"
path, err := machine.GetDataDir(v.Name)
if err != nil {
- logrus.Errorf("Error resolving data dir: %s", err.Error())
- return "", nil
+ logrus.Errorf("Resolving data dir: %s", err.Error())
+ return nil, err
+ }
+ return NewMachineFile(filepath.Join(path, sockName), &sockName)
+}
+
+func (v *MachineVM) setConfigPath() error {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ configPath, err := NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil)
+ if err != nil {
+ return err
+ }
+ v.ConfigPath = *configPath
+ return nil
+}
+
+func (v *MachineVM) setReadySocket() error {
+ readySocketName := v.Name + "_ready.sock"
+ rtPath, err := getRuntimeDir()
+ if err != nil {
+ return err
+ }
+ virtualSocketPath, err := NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName)
+ if err != nil {
+ return err
+ }
+ v.ReadySocket = *virtualSocketPath
+ return nil
+}
+
+func (v *MachineVM) setPIDSocket() error {
+ rtPath, err := getRuntimeDir()
+ if err != nil {
+ return err
+ }
+ if !rootless.IsRootless() {
+ rtPath = "/run"
+ }
+ pidFileName := fmt.Sprintf("%s.pid", v.Name)
+ socketDir := filepath.Join(rtPath, "podman")
+ pidFilePath, err := NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName)
+ if err != nil {
+ return err
}
- return filepath.Join(path, "podman.sock"), nil
+ v.PidFilePath = *pidFilePath
+ return nil
}
+// Deprecated: getSocketandPid is being replace by setPIDSocket and
+// machinefiles.
func (v *MachineVM) getSocketandPid() (string, string, error) {
rtPath, err := getRuntimeDir()
if err != nil {
@@ -1085,10 +1282,13 @@ func waitAndPingAPI(sock string) {
Transport: &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
con, err := net.DialTimeout("unix", sock, apiUpTimeout)
- if err == nil {
- con.SetDeadline(time.Now().Add(apiUpTimeout))
+ if err != nil {
+ return nil, err
}
- return con, err
+ if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil {
+ return nil, err
+ }
+ return con, nil
},
},
}
@@ -1102,66 +1302,119 @@ func waitAndPingAPI(sock string) {
}
}
-func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) {
- if forwardState != noForwarding {
- waitAndPingAPI(forwardSock)
- if !rootFul {
- fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
- fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
- fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
-
- suffix := ""
- if name != machine.DefaultMachineName {
- suffix = " " + name
+func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string) {
+ suffix := ""
+ if v.Name != machine.DefaultMachineName {
+ suffix = " " + v.Name
+ }
+
+ if v.isIncompatible() {
+ fmt.Fprintf(os.Stderr, "\n!!! ACTION REQUIRED: INCOMPATIBLE MACHINE !!!\n")
+
+ fmt.Fprintf(os.Stderr, "\nThis machine was created by an older podman release that is incompatible\n")
+ fmt.Fprintf(os.Stderr, "with this release of podman. It has been started in a limited operational\n")
+ fmt.Fprintf(os.Stderr, "mode to allow you to copy any necessary files before recreating it. This\n")
+ fmt.Fprintf(os.Stderr, "can be accomplished with the following commands:\n\n")
+ fmt.Fprintf(os.Stderr, "\t# Login and copy desired files (Optional)\n")
+ fmt.Fprintf(os.Stderr, "\t# podman machine ssh%s tar cvPf - /path/to/files > backup.tar\n\n", suffix)
+ fmt.Fprintf(os.Stderr, "\t# Recreate machine (DESTRUCTIVE!) \n")
+ fmt.Fprintf(os.Stderr, "\tpodman machine stop%s\n", suffix)
+ fmt.Fprintf(os.Stderr, "\tpodman machine rm -f%s\n", suffix)
+ fmt.Fprintf(os.Stderr, "\tpodman machine init --now%s\n\n", suffix)
+ fmt.Fprintf(os.Stderr, "\t# Copy back files (Optional)\n")
+ fmt.Fprintf(os.Stderr, "\t# cat backup.tar | podman machine ssh%s tar xvPf - \n\n", suffix)
+ }
+
+ if forwardState == noForwarding {
+ return
+ }
+
+ waitAndPingAPI(forwardSock)
+ if !v.Rootful {
+ fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
+ fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
+ fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ }
+
+ fmt.Printf("API forwarding listening on: %s\n", forwardSock)
+ if forwardState == dockerGlobal {
+ fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
+ } else {
+ stillString := "still "
+ switch forwardState {
+ case notInstalled:
+ fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
+ fmt.Printf("address can't be used by podman. ")
+ if helper := findClaimHelper(); len(helper) > 0 {
+ fmt.Printf("If you would like to install it run the\nfollowing commands:\n")
+ fmt.Printf("\n\tsudo %s install\n", helper)
+ fmt.Printf("\tpodman machine stop%s; podman machine start%s\n\n", suffix, suffix)
}
- fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ case machineLocal:
+ fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n")
+ case claimUnsupported:
+ fallthrough
+ default:
+ stillString = ""
}
- fmt.Printf("API forwarding listening on: %s\n", forwardSock)
- if forwardState == dockerGlobal {
- fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
- } else {
- stillString := "still "
- switch forwardState {
- case notInstalled:
- fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
- fmt.Printf("address can't be used by podman. ")
- if helper := findClaimHelper(); len(helper) > 0 {
- fmt.Printf("If you would like to install it run the\nfollowing command:\n")
- fmt.Printf("\n\tsudo %s install\n\n", helper)
- }
- case machineLocal:
- fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n")
- case claimUnsupported:
- fallthrough
- default:
- stillString = ""
- }
+ fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
+ fmt.Printf("following command in your terminal session:\n")
+ fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
+ }
+}
- fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
- fmt.Printf("following command in your terminal session:\n")
- fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
+// update returns the content of the VM's
+// configuration file in json
+func (v *MachineVM) update() error {
+ if err := v.setConfigPath(); err != nil {
+ return err
+ }
+ b, err := v.ConfigPath.Read()
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return errors.Wrap(machine.ErrNoSuchVM, v.Name)
+ }
+ return err
+ }
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, v)
+ if err != nil {
+ err = migrateVM(v.ConfigPath.GetPath(), b, v)
+ if err != nil {
+ return err
}
}
+ return err
}
func (v *MachineVM) writeConfig() error {
- // GetConfDir creates the directory so no need to check for
- // its existence
- vmConfigDir, err := machine.GetConfDir(vmtype)
- if err != nil {
+ // Set the path of the configfile before writing to make
+ // life easier down the line
+ if err := v.setConfigPath(); err != nil {
return err
}
-
- jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
// Write the JSON file
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
- if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ if err := ioutil.WriteFile(v.ConfigPath.GetPath(), b, 0644); err != nil {
return err
}
-
return nil
}
+
+// getImageFile wrapper returns the path to the image used
+// to boot the VM
+func (v *MachineVM) getImageFile() string {
+ return v.ImagePath.GetPath()
+}
+
+// getIgnitionFile wrapper returns the path to the ignition file
+func (v *MachineVM) getIgnitionFile() string {
+ return v.IgnitionFilePath.GetPath()
+}
diff --git a/pkg/machine/qemu/machine_unsupported.go b/pkg/machine/qemu/machine_unsupported.go
index e3ce05e3d..794e710f9 100644
--- a/pkg/machine/qemu/machine_unsupported.go
+++ b/pkg/machine/qemu/machine_unsupported.go
@@ -1,3 +1,4 @@
+//go:build (!amd64 && !arm64) || windows
// +build !amd64,!arm64 windows
package qemu
diff --git a/pkg/machine/qemu/options_darwin_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go
index 5b6cdc86d..4c954af00 100644
--- a/pkg/machine/qemu/options_darwin_arm64.go
+++ b/pkg/machine/qemu/options_darwin_arm64.go
@@ -11,7 +11,7 @@ var (
)
func (v *MachineVM) addArchOptions() []string {
- ovmfDir := getOvmfDir(v.ImagePath, v.Name)
+ ovmfDir := getOvmfDir(v.ImagePath.GetPath(), v.Name)
opts := []string{
"-accel", "hvf",
"-accel", "tcg",
@@ -23,13 +23,13 @@ func (v *MachineVM) addArchOptions() []string {
}
func (v *MachineVM) prepare() error {
- ovmfDir := getOvmfDir(v.ImagePath, v.Name)
+ ovmfDir := getOvmfDir(v.ImagePath.GetPath(), v.Name)
cmd := []string{"/bin/dd", "if=/dev/zero", "conv=sync", "bs=1m", "count=64", "of=" + ovmfDir}
return exec.Command(cmd[0], cmd[1:]...).Run()
}
func (v *MachineVM) archRemovalFiles() []string {
- ovmDir := getOvmfDir(v.ImagePath, v.Name)
+ ovmDir := getOvmfDir(v.ImagePath.GetPath(), v.Name)
return []string{ovmDir}
}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 5b0c757f0..fdda45ca6 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "io/fs"
"io/ioutil"
"net/url"
"os"
@@ -144,6 +145,8 @@ http://docs.microsoft.com/en-us/windows/wsl/install\
const (
winSShProxy = "win-sshproxy.exe"
winSshProxyTid = "win-sshproxy.tid"
+ pipePrefix = "npipe:////./pipe/"
+ globalPipe = "docker_engine"
)
type Provider struct{}
@@ -800,16 +803,15 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
}
func launchWinProxy(v *MachineVM) (bool, string, error) {
- globalName := true
- pipeName := "docker_engine"
- if !pipeAvailable(pipeName) {
- pipeName = toDist(v.Name)
- globalName = false
- if !pipeAvailable(pipeName) {
- return globalName, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", pipeName)
- }
+ machinePipe := toDist(v.Name)
+ if !pipeAvailable(machinePipe) {
+ return false, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
+ }
+
+ globalName := false
+ if pipeAvailable(globalPipe) {
+ globalName = true
}
- fullPipeName := "npipe:////./pipe/" + pipeName
exe, err := os.Executable()
if err != nil {
@@ -828,12 +830,19 @@ func launchWinProxy(v *MachineVM) (bool, string, error) {
}
dest := fmt.Sprintf("ssh://root@localhost:%d/run/podman/podman.sock", v.Port)
- cmd := exec.Command(command, v.Name, stateDir, fullPipeName, dest, v.IdentityPath)
+ args := []string{v.Name, stateDir, pipePrefix + machinePipe, dest, v.IdentityPath}
+ waitPipe := machinePipe
+ if globalName {
+ args = append(args, pipePrefix+globalPipe, dest, v.IdentityPath)
+ waitPipe = globalPipe
+ }
+
+ cmd := exec.Command(command, args...)
if err := cmd.Start(); err != nil {
return globalName, "", err
}
- return globalName, fullPipeName, waitPipeExists(pipeName, 30, func() error {
+ return globalName, pipePrefix + waitPipe, waitPipeExists(waitPipe, 30, func() error {
active, exitCode := getProcessState(cmd.Process.Pid)
if !active {
return errors.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
@@ -1175,10 +1184,10 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
var listed []*machine.ListResponse
- if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
vm := new(MachineVM)
- if strings.HasSuffix(info.Name(), ".json") {
- fullPath := filepath.Join(vmConfigDir, info.Name())
+ if strings.HasSuffix(d.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, d.Name())
b, err := ioutil.ReadFile(fullPath)
if err != nil {
return err
diff --git a/pkg/machine/wsl/machine_unsupported.go b/pkg/machine/wsl/machine_unsupported.go
index 043c5d729..856f9dd0c 100644
--- a/pkg/machine/wsl/machine_unsupported.go
+++ b/pkg/machine/wsl/machine_unsupported.go
@@ -1,3 +1,4 @@
+//go:build !windows
// +build !windows
package wsl