diff options
Diffstat (limited to 'pkg/machine')
-rw-r--r-- | pkg/machine/config.go | 9 | ||||
-rw-r--r-- | pkg/machine/config_test.go | 71 | ||||
-rw-r--r-- | pkg/machine/fcos.go | 1 | ||||
-rw-r--r-- | pkg/machine/fedora.go | 6 | ||||
-rw-r--r-- | pkg/machine/ignition.go | 5 | ||||
-rw-r--r-- | pkg/machine/ignition_darwin.go | 3 | ||||
-rw-r--r-- | pkg/machine/ignition_schema.go | 1 | ||||
-rw-r--r-- | pkg/machine/ignition_windows.go | 3 | ||||
-rw-r--r-- | pkg/machine/keys.go | 1 | ||||
-rw-r--r-- | pkg/machine/machine_unsupported.go | 1 | ||||
-rw-r--r-- | pkg/machine/pull.go | 7 | ||||
-rw-r--r-- | pkg/machine/qemu/config.go | 169 | ||||
-rw-r--r-- | pkg/machine/qemu/config_test.go | 133 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 575 | ||||
-rw-r--r-- | pkg/machine/qemu/machine_unsupported.go | 1 | ||||
-rw-r--r-- | pkg/machine/qemu/options_darwin_arm64.go | 6 | ||||
-rw-r--r-- | pkg/machine/wsl/machine.go | 37 | ||||
-rw-r--r-- | pkg/machine/wsl/machine_unsupported.go | 1 |
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 |