diff options
Diffstat (limited to 'pkg/machine')
-rw-r--r-- | pkg/machine/config.go | 50 | ||||
-rw-r--r-- | pkg/machine/config_test.go | 3 | ||||
-rw-r--r-- | pkg/machine/connection.go | 4 | ||||
-rw-r--r-- | pkg/machine/e2e/config.go | 21 | ||||
-rw-r--r-- | pkg/machine/e2e/config_info.go | 20 | ||||
-rw-r--r-- | pkg/machine/e2e/info_test.go | 58 | ||||
-rw-r--r-- | pkg/machine/e2e/init_test.go | 19 | ||||
-rw-r--r-- | pkg/machine/e2e/inspect_test.go | 22 | ||||
-rw-r--r-- | pkg/machine/e2e/list_test.go | 34 | ||||
-rw-r--r-- | pkg/machine/e2e/machine_test.go | 9 | ||||
-rw-r--r-- | pkg/machine/e2e/set_test.go | 13 | ||||
-rw-r--r-- | pkg/machine/e2e/ssh_test.go | 7 | ||||
-rw-r--r-- | pkg/machine/fcos.go | 12 | ||||
-rw-r--r-- | pkg/machine/fedora.go | 6 | ||||
-rw-r--r-- | pkg/machine/ignition.go | 4 | ||||
-rw-r--r-- | pkg/machine/keys.go | 30 | ||||
-rw-r--r-- | pkg/machine/qemu/config.go | 4 | ||||
-rw-r--r-- | pkg/machine/qemu/config_test.go | 3 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 259 | ||||
-rw-r--r-- | pkg/machine/qemu/machine_test.go | 3 | ||||
-rw-r--r-- | pkg/machine/qemu/options_darwin_arm64.go | 23 | ||||
-rw-r--r-- | pkg/machine/wsl/machine.go | 203 | ||||
-rw-r--r-- | pkg/machine/wsl/util_windows.go | 30 |
23 files changed, 609 insertions, 228 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go index d34776714..29cd7bc00 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -4,7 +4,7 @@ package machine import ( - errors2 "errors" + "errors" "io/ioutil" "net" "net/url" @@ -13,7 +13,6 @@ import ( "time" "github.com/containers/storage/pkg/homedir" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -42,7 +41,9 @@ const ( // Running indicates the qemu vm is running. Running Status = "running" // Stopped indicates the vm has stopped. - Stopped Status = "stopped" + Stopped Status = "stopped" + // Starting indicated the vm is in the process of starting + Starting Status = "starting" DefaultMachineName string = "podman-machine-default" ) @@ -53,6 +54,7 @@ type Provider interface { IsValidVMName(name string) (bool, error) CheckExclusiveActiveVM() (bool, string, error) RemoveAndCleanMachines() error + VMType() string } type RemoteConnectionType string @@ -62,7 +64,7 @@ var ( DefaultIgnitionUserName = "core" ErrNoSuchVM = errors.New("VM does not exist") ErrVMAlreadyExists = errors.New("VM already exists") - ErrVMAlreadyRunning = errors.New("VM already running") + ErrVMAlreadyRunning = errors.New("VM already running or starting") ErrMultipleActiveVM = errors.New("only one VM can be active at a time") ForwarderBinaryName = "gvproxy" ) @@ -88,6 +90,7 @@ type ListResponse struct { CreatedAt time.Time LastUp time.Time Running bool + Starting bool Stream string VMType string CPUs uint64 @@ -138,14 +141,15 @@ type DistributionDownload interface { Get() *Download } type InspectInfo struct { - ConfigPath VMFile - Created time.Time - Image ImageConfig - LastUp time.Time - Name string - Resources ResourceConfig - SSHConfig SSHConfig - State Status + ConfigPath VMFile + ConnectionInfo ConnectionConfig + Created time.Time + Image ImageConfig + LastUp time.Time + Name string + Resources ResourceConfig + SSHConfig SSHConfig + State Status } func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { @@ -251,11 +255,11 @@ func (m *VMFile) GetPath() string { // the actual path func (m *VMFile) Delete() error { if m.Symlink != nil { - if err := os.Remove(*m.Symlink); err != nil && !errors2.Is(err, os.ErrNotExist) { + 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 && !errors2.Is(err, os.ErrNotExist) { + if err := os.Remove(m.Path); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil @@ -269,14 +273,14 @@ func (m *VMFile) Read() ([]byte, error) { // NewMachineFile is a constructor for VMFile func NewMachineFile(path string, symlink *string) (*VMFile, error) { if len(path) < 1 { - return nil, errors2.New("invalid machine file path") + return nil, errors.New("invalid machine file path") } if symlink != nil && len(*symlink) < 1 { - return nil, errors2.New("invalid symlink path") + return nil, errors.New("invalid symlink path") } mf := VMFile{Path: path} if symlink != nil && len(path) > maxSocketPathLength { - if err := mf.makeSymlink(symlink); err != nil && !errors2.Is(err, os.ErrExist) { + if err := mf.makeSymlink(symlink); err != nil && !errors.Is(err, os.ErrExist) { return nil, err } } @@ -286,13 +290,13 @@ func NewMachineFile(path string, symlink *string) (*VMFile, error) { // makeSymlink for macOS creates a symlink in $HOME/.podman/ // for a machinefile like a socket func (m *VMFile) makeSymlink(symlink *string) error { - homedir, err := os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { return err } - sl := filepath.Join(homedir, ".podman", *symlink) + 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 && !errors2.Is(err, os.ErrNotExist) { + if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors.Is(err, os.ErrNotExist) { return err } m.Symlink = &sl @@ -335,3 +339,9 @@ type SSHConfig struct { // RemoteUsername of the vm user RemoteUsername string } + +// ConnectionConfig contains connections like sockets, etc. +type ConnectionConfig struct { + // PodmanSocket is the exported podman service socket + PodmanSocket *VMFile `json:"PodmanSocket"` +} diff --git a/pkg/machine/config_test.go b/pkg/machine/config_test.go index d9fc5425e..ca08660b9 100644 --- a/pkg/machine/config_test.go +++ b/pkg/machine/config_test.go @@ -1,3 +1,6 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + package machine import ( diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go index 841b2afa6..6ff761a92 100644 --- a/pkg/machine/connection.go +++ b/pkg/machine/connection.go @@ -4,10 +4,10 @@ package machine import ( + "errors" "fmt" "github.com/containers/common/pkg/config" - "github.com/pkg/errors" ) func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) error { @@ -72,7 +72,7 @@ func RemoveConnection(name string) error { if _, ok := cfg.Engine.ServiceDestinations[name]; ok { delete(cfg.Engine.ServiceDestinations, name) } else { - return errors.Errorf("unable to find connection named %q", name) + return fmt.Errorf("unable to find connection named %q", name) } return cfg.Write() } diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config.go index c17b840d3..b3fe74b0c 100644 --- a/pkg/machine/e2e/config.go +++ b/pkg/machine/e2e/config.go @@ -3,7 +3,6 @@ package e2e import ( "encoding/json" "fmt" - "math/rand" "os" "os/exec" "path/filepath" @@ -13,6 +12,7 @@ import ( "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/qemu" "github.com/containers/podman/v4/pkg/util" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" //nolint:golint,stylecheck . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -85,6 +85,14 @@ func (ms *machineSession) outputToString() string { return strings.Join(fields, " ") } +// errorToString returns the error output from a session in string form +func (ms *machineSession) errorToString() string { + if ms == nil || ms.Err == nil || ms.Err.Contents() == nil { + return "" + } + return string(ms.Err.Contents()) +} + // newMB constructor for machine test builders func newMB() (*machineTestBuilder, error) { mb := machineTestBuilder{ @@ -128,14 +136,14 @@ func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuild // toQemuInspectInfo is only for inspecting qemu machines. Other providers will need // to make their own. -func (mb *machineTestBuilder) toQemuInspectInfo() ([]qemuMachineInspectInfo, int, error) { +func (mb *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) { args := []string{"machine", "inspect"} args = append(args, mb.names...) session, err := runWrapper(mb.podmanBinary, args, defaultTimeout, true) if err != nil { return nil, -1, err } - mii := []qemuMachineInspectInfo{} + mii := []machine.InspectInfo{} err = json.Unmarshal(session.Bytes(), &mii) return mii, session.ExitCode(), err } @@ -171,10 +179,5 @@ func (m *machineTestBuilder) init() {} // randomString returns a string of given length composed of random characters func randomString(n int) string { - var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]rune, n) - for i := range b { - b[i] = randomLetters[rand.Intn(len(randomLetters))] - } - return string(b) + return stringid.GenerateRandomID()[0:12] } diff --git a/pkg/machine/e2e/config_info.go b/pkg/machine/e2e/config_info.go new file mode 100644 index 000000000..410c7e518 --- /dev/null +++ b/pkg/machine/e2e/config_info.go @@ -0,0 +1,20 @@ +package e2e + +type infoMachine struct { + format string + cmd []string +} + +func (i *infoMachine) buildCmd(m *machineTestBuilder) []string { + cmd := []string{"machine", "info"} + if len(i.format) > 0 { + cmd = append(cmd, "--format", i.format) + } + i.cmd = cmd + return cmd +} + +func (i *infoMachine) withFormat(format string) *infoMachine { + i.format = format + return i +} diff --git a/pkg/machine/e2e/info_test.go b/pkg/machine/e2e/info_test.go new file mode 100644 index 000000000..eeabb78af --- /dev/null +++ b/pkg/machine/e2e/info_test.go @@ -0,0 +1,58 @@ +package e2e + +import ( + "github.com/containers/podman/v4/cmd/podman/machine" + jsoniter "github.com/json-iterator/go" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman machine info", func() { + var ( + mb *machineTestBuilder + testDir string + ) + + BeforeEach(func() { + testDir, mb = setup() + }) + AfterEach(func() { + teardown(originalHomeDir, testDir, mb) + }) + + It("machine info", func() { + info := new(infoMachine) + infoSession, err := mb.setCmd(info).run() + Expect(err).NotTo(HaveOccurred()) + Expect(infoSession).Should(Exit(0)) + + // Verify go template works and check for no running machines + info = new(infoMachine) + infoSession, err = mb.setCmd(info.withFormat("{{.Host.NumberOfMachines}}")).run() + Expect(err).NotTo(HaveOccurred()) + Expect(infoSession).Should(Exit(0)) + Expect(infoSession.outputToString()).To(Equal("0")) + + // Create a machine and check if info has been updated + i := new(initMachine) + initSession, err := mb.setCmd(i.withImagePath(mb.imagePath)).run() + Expect(err).To(BeNil()) + Expect(initSession).To(Exit(0)) + + info = new(infoMachine) + infoSession, err = mb.setCmd(info.withFormat("{{.Host.NumberOfMachines}}")).run() + Expect(err).NotTo(HaveOccurred()) + Expect(infoSession).Should(Exit(0)) + Expect(infoSession.outputToString()).To(Equal("1")) + + // Check if json is in correct format + infoSession, err = mb.setCmd(info.withFormat("json")).run() + Expect(err).NotTo(HaveOccurred()) + Expect(infoSession).Should(Exit(0)) + + infoReport := &machine.Info{} + err = jsoniter.Unmarshal(infoSession.Bytes(), infoReport) + Expect(err).To(BeNil()) + }) +}) diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go index 6949eb0af..40f140cae 100644 --- a/pkg/machine/e2e/init_test.go +++ b/pkg/machine/e2e/init_test.go @@ -3,6 +3,7 @@ package e2e import ( "io/ioutil" "os" + "runtime" "time" "github.com/containers/podman/v4/pkg/machine" @@ -44,9 +45,9 @@ var _ = Describe("podman machine init", func() { Expect(len(inspectBefore)).To(BeNumerically(">", 0)) testMachine := inspectBefore[0] - Expect(testMachine.VM.Name).To(Equal(mb.names[0])) - Expect(testMachine.VM.CPUs).To(Equal(uint64(1))) - Expect(testMachine.VM.Memory).To(Equal(uint64(2048))) + Expect(testMachine.Name).To(Equal(mb.names[0])) + Expect(testMachine.Resources.CPUs).To(Equal(uint64(1))) + Expect(testMachine.Resources.Memory).To(Equal(uint64(2048))) }) @@ -61,7 +62,7 @@ var _ = Describe("podman machine init", func() { Expect(len(inspectBefore)).To(BeNumerically(">", 0)) Expect(err).To(BeNil()) Expect(len(inspectBefore)).To(BeNumerically(">", 0)) - Expect(inspectBefore[0].VM.Name).To(Equal(mb.names[0])) + Expect(inspectBefore[0].Name).To(Equal(mb.names[0])) s := startMachine{} ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run() @@ -104,7 +105,15 @@ var _ = Describe("podman machine init", func() { memorySession, err := mb.setName(name).setCmd(sshMemory.withSSHComand([]string{"cat", "/proc/meminfo", "|", "numfmt", "--field", "2", "--from-unit=Ki", "--to-unit=Mi", "|", "sed", "'s/ kB/M/g'", "|", "grep", "MemTotal"})).run() Expect(err).To(BeNil()) Expect(memorySession).To(Exit(0)) - Expect(memorySession.outputToString()).To(ContainSubstring("3824")) + switch runtime.GOOS { + // os's handle memory differently + case "linux": + Expect(memorySession.outputToString()).To(ContainSubstring("3821")) + case "darwin": + Expect(memorySession.outputToString()).To(ContainSubstring("3824")) + default: + // add windows when testing on that platform + } sshTimezone := sshMachine{} timezoneSession, err := mb.setName(name).setCmd(sshTimezone.withSSHComand([]string{"date"})).run() diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index 2c9de5664..93fb8cc2b 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -1,10 +1,9 @@ package e2e import ( - "encoding/json" + "strings" "github.com/containers/podman/v4/pkg/machine" - "github.com/containers/podman/v4/pkg/machine/qemu" jsoniter "github.com/json-iterator/go" . "github.com/onsi/ginkgo" @@ -50,24 +49,6 @@ var _ = Describe("podman machine stop", func() { Expect(err).To(BeNil()) Expect(inspectSession).To(Exit(0)) Expect(inspectSession.Bytes()).To(ContainSubstring("foo1")) - - type fakeInfos struct { - Status string - VM qemu.MachineVM - } - infos := make([]fakeInfos, 0, 2) - err = json.Unmarshal(inspectSession.Bytes(), &infos) - Expect(err).ToNot(HaveOccurred()) - Expect(len(infos)).To(Equal(2)) - - // rm := new(rmMachine) - // // Must manually clean up due to multiple names - // for _, name := range []string{"foo1", "foo2"} { - // mb.setName(name).setCmd(rm.withForce()).run() - // mb.names = []string{} - // } - // mb.names = []string{} - }) It("inspect with go format", func() { @@ -86,6 +67,7 @@ var _ = Describe("podman machine stop", func() { var inspectInfo []machine.InspectInfo err = jsoniter.Unmarshal(inspectSession.Bytes(), &inspectInfo) Expect(err).To(BeNil()) + Expect(strings.HasSuffix(inspectInfo[0].ConnectionInfo.PodmanSocket.GetPath(), "podman.sock")) inspect := new(inspectMachine) inspect = inspect.withFormat("{{.Name}}") diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go index 0bc867047..fb855c61e 100644 --- a/pkg/machine/e2e/list_test.go +++ b/pkg/machine/e2e/list_test.go @@ -2,9 +2,10 @@ package e2e import ( "strings" + "time" - "github.com/containers/buildah/util" - "github.com/containers/podman/v4/cmd/podman/machine" + "github.com/containers/common/pkg/util" + "github.com/containers/podman/v4/pkg/domain/entities" jsoniter "github.com/json-iterator/go" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -29,7 +30,7 @@ var _ = Describe("podman machine list", func() { firstList, err := mb.setCmd(list).run() Expect(err).NotTo(HaveOccurred()) Expect(firstList).Should(Exit(0)) - Expect(len(firstList.outputToStringSlice())).To(Equal(1)) // just the header + Expect(firstList.outputToStringSlice()).To(HaveLen(1)) // just the header i := new(initMachine) session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run() @@ -39,7 +40,7 @@ var _ = Describe("podman machine list", func() { secondList, err := mb.setCmd(list).run() Expect(err).NotTo(HaveOccurred()) Expect(secondList).To(Exit(0)) - Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // one machine and the header + Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // one machine and the header }) It("list machines with quiet or noheading", func() { @@ -51,12 +52,12 @@ var _ = Describe("podman machine list", func() { firstList, err := mb.setCmd(list.withQuiet()).run() Expect(err).NotTo(HaveOccurred()) Expect(firstList).Should(Exit(0)) - Expect(len(firstList.outputToStringSlice())).To(Equal(0)) // No header with quiet + Expect(firstList.outputToStringSlice()).To(HaveLen(0)) // No header with quiet noheaderSession, err := mb.setCmd(list.withNoHeading()).run() // noheader Expect(err).NotTo(HaveOccurred()) Expect(noheaderSession).Should(Exit(0)) - Expect(len(noheaderSession.outputToStringSlice())).To(Equal(0)) + Expect(noheaderSession.outputToStringSlice()).To(HaveLen(0)) i := new(initMachine) session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run() @@ -70,7 +71,7 @@ var _ = Describe("podman machine list", func() { secondList, err := mb.setCmd(list.withQuiet()).run() Expect(err).NotTo(HaveOccurred()) Expect(secondList).To(Exit(0)) - Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // two machines, no header + Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // two machines, no header listNames := secondList.outputToStringSlice() stripAsterisk(listNames) @@ -87,7 +88,7 @@ var _ = Describe("podman machine list", func() { startSession, err := mb.setCmd(s).runWithoutWait() Expect(err).To(BeNil()) l := new(listMachine) - for { // needs to be infinite because we need to check if running when inspect returns to avoid race conditions. + for i := 0; i < 30; i++ { listSession, err := mb.setCmd(l).run() Expect(listSession).To(Exit(0)) Expect(err).To(BeNil()) @@ -96,6 +97,7 @@ var _ = Describe("podman machine list", func() { } else { break } + time.Sleep(3 * time.Second) } Expect(startSession).To(Exit(0)) listSession, err := mb.setCmd(l).run() @@ -116,10 +118,10 @@ var _ = Describe("podman machine list", func() { // go format list := new(listMachine) - listSession, err := mb.setCmd(list.withFormat("{{.Name}}").withNoHeading()).run() + listSession, err := mb.setCmd(list.withFormat("{{.Name}}")).run() Expect(err).NotTo(HaveOccurred()) Expect(listSession).To(Exit(0)) - Expect(len(listSession.outputToStringSlice())).To(Equal(1)) + Expect(listSession.outputToStringSlice()).To(HaveLen(1)) listNames := listSession.outputToStringSlice() stripAsterisk(listNames) @@ -128,13 +130,21 @@ var _ = Describe("podman machine list", func() { // --format json list2 := new(listMachine) list2 = list2.withFormat("json") - listSession2, err := mb.setName("foo1").setCmd(list2).run() + listSession2, err := mb.setCmd(list2).run() Expect(err).To(BeNil()) Expect(listSession2).To(Exit(0)) - var listResponse []*machine.ListReporter + var listResponse []*entities.ListReporter err = jsoniter.Unmarshal(listSession.Bytes(), &listResponse) Expect(err).To(BeNil()) + + // table format includes the header + list = new(listMachine) + listSession3, err3 := mb.setCmd(list.withFormat("table {{.Name}}")).run() + Expect(err3).NotTo(HaveOccurred()) + Expect(listSession3).To(Exit(0)) + listNames3 := listSession3.outputToStringSlice() + Expect(listNames3).To(HaveLen(2)) }) }) diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index 657014b05..7b063937d 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -22,7 +22,7 @@ func TestMain(m *testing.M) { } const ( - defaultStream string = "podman-testing" + defaultStream string = "testing" ) var ( @@ -97,6 +97,9 @@ func setup() (string, *machineTestBuilder) { if err := os.Setenv("HOME", homeDir); err != nil { Fail("failed to set home dir") } + if err := os.Setenv("XDG_RUNTIME_DIR", homeDir); err != nil { + Fail("failed to set xdg_runtime dir") + } if err := os.Unsetenv("SSH_AUTH_SOCK"); err != nil { Fail("unable to unset SSH_AUTH_SOCK") } @@ -120,9 +123,9 @@ func setup() (string, *machineTestBuilder) { } func teardown(origHomeDir string, testDir string, mb *machineTestBuilder) { - s := new(stopMachine) + r := new(rmMachine) for _, name := range mb.names { - if _, err := mb.setName(name).setCmd(s).run(); err != nil { + if _, err := mb.setName(name).setCmd(r.withForce()).run(); err != nil { fmt.Printf("error occurred rm'ing machine: %q\n", err) } } diff --git a/pkg/machine/e2e/set_test.go b/pkg/machine/e2e/set_test.go index 15215a44d..80cb89488 100644 --- a/pkg/machine/e2e/set_test.go +++ b/pkg/machine/e2e/set_test.go @@ -1,6 +1,8 @@ package e2e import ( + "runtime" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -57,8 +59,15 @@ var _ = Describe("podman machine set", func() { memorySession, err := mb.setName(name).setCmd(sshMemory.withSSHComand([]string{"cat", "/proc/meminfo", "|", "numfmt", "--field", "2", "--from-unit=Ki", "--to-unit=Mi", "|", "sed", "'s/ kB/M/g'", "|", "grep", "MemTotal"})).run() Expect(err).To(BeNil()) Expect(memorySession).To(Exit(0)) - Expect(memorySession.outputToString()).To(ContainSubstring("3824")) - + switch runtime.GOOS { + // it seems macos and linux handle memory differently + case "linux": + Expect(memorySession.outputToString()).To(ContainSubstring("3821")) + case "darwin": + Expect(memorySession.outputToString()).To(ContainSubstring("3824")) + default: + // windows can go here if we ever run tests there + } // Setting a running machine results in 125 runner, err := mb.setName(name).setCmd(set.withCPUs(4)).run() Expect(err).To(BeNil()) diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go index 155d39a64..9ee31ac26 100644 --- a/pkg/machine/e2e/ssh_test.go +++ b/pkg/machine/e2e/ssh_test.go @@ -56,5 +56,12 @@ var _ = Describe("podman machine ssh", func() { Expect(err).To(BeNil()) Expect(sshSession).To(Exit(0)) Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS")) + + // keep exit code + sshSession, err = mb.setName(name).setCmd(ssh.withSSHComand([]string{"false"})).run() + Expect(err).To(BeNil()) + Expect(sshSession).To(Exit(1)) + Expect(sshSession.outputToString()).To(Equal("")) + Expect(sshSession.errorToString()).To(Equal("")) }) }) diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index df58b8a1e..4ccb99e96 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -17,7 +17,6 @@ import ( "github.com/coreos/stream-metadata-go/fedoracoreos" "github.com/coreos/stream-metadata-go/release" "github.com/coreos/stream-metadata-go/stream" - "github.com/pkg/errors" digest "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" @@ -139,20 +138,13 @@ func getStreamURL(streamType string) url2.URL { // This should get Exported and stay put as it will apply to all fcos downloads // getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version -func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:staticcheck +func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { var ( fcosstable stream.Stream altMeta release.Release streamType string ) - // This is being hard set to testing. Once podman4 is in the - // fcos trees, we should remove it and re-release at least on - // macs. - // TODO: remove when podman4.0 is in coreos - - imageStream = "podman-testing" //nolint:staticcheck - switch imageStream { case "podman-testing": streamType = "podman-testing" @@ -163,7 +155,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:s case "stable": streamType = fedoracoreos.StreamStable default: - return nil, errors.Errorf("invalid stream %s: valid streams are `testing` and `stable`", imageStream) + return nil, fmt.Errorf("invalid stream %s: valid streams are `testing` and `stable`", imageStream) } streamurl := getStreamURL(streamType) resp, err := http.Get(streamurl.String()) diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go index 14a173da6..46e450418 100644 --- a/pkg/machine/fedora.go +++ b/pkg/machine/fedora.go @@ -4,6 +4,7 @@ package machine import ( + "errors" "fmt" "io" "io/ioutil" @@ -13,7 +14,6 @@ import ( "path/filepath" "regexp" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -107,12 +107,12 @@ func getFedoraDownload(releaseStream string) (string, *url.URL, int64, error) { newLocation := rawURL + file downloadURL, err := url.Parse(newLocation) if err != nil { - return "", nil, -1, errors.Wrapf(err, "invalid URL generated from discovered Fedora file: %s", newLocation) + return "", nil, -1, fmt.Errorf("invalid URL generated from discovered Fedora file: %s: %w", newLocation, err) } resp, err := http.Head(newLocation) if err != nil { - return "", nil, -1, errors.Wrapf(err, "head request failed: %s", newLocation) + return "", nil, -1, fmt.Errorf("head request failed: %s: %w", newLocation, err) } _ = resp.Body.Close() diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 35a9a30cb..f4602cc95 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -93,7 +93,7 @@ func NewIgnitionFile(ign DynamicIgnition) error { tz string ) // local means the same as the host - // lookup where it is pointing to on the host + // look up where it is pointing to on the host if ign.TimeZone == "local" { tz, err = getLocalTimeZone() if err != nil { @@ -348,7 +348,7 @@ Delegate=memory pids cpu io }, }) - // Setup /etc/subuid and /etc/subgid + // Set up /etc/subuid and /etc/subgid for _, sub := range []string{"/etc/subuid", "/etc/subgid"} { files = append(files, File{ Node: Node{ diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go index 15c1f73d8..0c7d7114e 100644 --- a/pkg/machine/keys.go +++ b/pkg/machine/keys.go @@ -6,6 +6,7 @@ package machine import ( "errors" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -51,7 +52,23 @@ func CreateSSHKeysPrefix(dir string, file string, passThru bool, skipExisting bo // generatekeys creates an ed25519 set of keys func generatekeys(writeLocation string) error { args := append(append([]string{}, sshCommand[1:]...), writeLocation) - return exec.Command(sshCommand[0], args...).Run() + cmd := exec.Command(sshCommand[0], args...) + stdErr, err := cmd.StderrPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + waitErr := cmd.Wait() + if waitErr == nil { + return nil + } + errMsg, err := io.ReadAll(stdErr) + if err != nil { + return fmt.Errorf("key generation failed, unable to read from stderr: %w", waitErr) + } + return fmt.Errorf("failed to generate keys: %s: %w", string(errMsg), waitErr) } // generatekeys creates an ed25519 set of keys @@ -59,7 +76,16 @@ func generatekeysPrefix(dir string, file string, passThru bool, prefix ...string args := append([]string{}, prefix[1:]...) args = append(args, sshCommand...) args = append(args, file) - cmd := exec.Command(prefix[0], args...) + + binary, err := exec.LookPath(prefix[0]) + if err != nil { + return err + } + binary, err = filepath.Abs(binary) + if err != nil { + return err + } + cmd := exec.Command(binary, args...) cmd.Dir = dir if passThru { cmd.Stdin = os.Stdin diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 56c95e3b3..bada1af9b 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -72,8 +72,10 @@ type MachineVM struct { Mounts []machine.Mount // Name of VM Name string - // PidFilePath is the where the PID file lives + // PidFilePath is the where the Proxy PID file lives PidFilePath machine.VMFile + // VMPidFilePath is the where the VM PID file lives + VMPidFilePath machine.VMFile // QMPMonitor is the qemu monitor object for sending commands QMPMonitor Monitor // ReadySocket tells host when vm is booted diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index 4d96ec6e7..72cb3ed90 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -1,3 +1,6 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) +// +build amd64,!windows arm64,!windows + package qemu import ( diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 6e36b0886..6134e69e1 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -5,9 +5,11 @@ package qemu import ( "bufio" + "bytes" "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io/fs" "io/ioutil" @@ -16,9 +18,11 @@ import ( "net/url" "os" "os/exec" + "os/signal" "path/filepath" "strconv" "strings" + "syscall" "time" "github.com/containers/common/pkg/config" @@ -28,8 +32,8 @@ import ( "github.com/containers/storage/pkg/homedir" "github.com/digitalocean/go-qemu/qmp" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) var ( @@ -105,6 +109,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { if err != nil { return nil, err } + if err := vm.setPIDSocket(); err != nil { + return nil, err + } cmd := []string{execPath} // Add memory cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) @@ -132,12 +139,12 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { cmd = append(cmd, []string{ "-device", "virtio-serial", // 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"}...) + // Note both id and chardev start with an extra "a" because qemu requires that it + // starts with an letter but users can also use numbers + "-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=a" + vm.Name + "_ready", + "-device", "virtserialport,chardev=a" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0", + "-pidfile", vm.VMPidFilePath.GetPath()}...) vm.CmdLine = cmd - if err := vm.setPIDSocket(); err != nil { - return nil, err - } return vm, nil } @@ -207,7 +214,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { vm.Rootful = old.Rootful vm.UID = old.UID - // Backup the original config file + // Back up the original config file if err := os.Rename(configPath, configPath+".orig"); err != nil { return err } @@ -314,6 +321,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { source := paths[0] target := source readonly := false + securityModel := "mapped-xattr" if len(paths) > 1 { target = paths[1] } @@ -321,18 +329,20 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { options := paths[2] volopts := strings.Split(options, ",") for _, o := range volopts { - switch o { - case "rw": + switch { + case o == "rw": readonly = false - case "ro": + case o == "ro": readonly = true + case strings.HasPrefix(o, "security_model="): + securityModel = strings.Split(o, "=")[1] default: fmt.Printf("Unknown option: %s\n", o) } } } if volumeType == VolumeTypeVirtfs { - virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag) + virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=%s", source, tag, securityModel) if readonly { virtfsOptions += ",readonly" } @@ -430,12 +440,12 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if v.Name != machine.DefaultMachineName { suffix = " " + v.Name } - return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) + return setErrors, fmt.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) } if opts.Rootful != nil && v.Rootful != *opts.Rootful { if err := v.setRootful(*opts.Rootful); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option")) + setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err)) } else { v.Rootful = *opts.Rootful } @@ -453,7 +463,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize { if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk")) + setErrors = append(setErrors, fmt.Errorf("failed to resize disk: %w", err)) } else { v.DiskSize = *opts.DiskSize } @@ -484,19 +494,33 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if err := v.writeConfig(); err != nil { return fmt.Errorf("writing JSON file: %w", err) } - defer func() { + doneStarting := func() { v.Starting = false if err := v.writeConfig(); err != nil { logrus.Errorf("Writing JSON file: %v", err) } + } + defer doneStarting() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + _, ok := <-c + if !ok { + return + } + doneStarting() + os.Exit(1) }() + defer close(c) + 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) + return fmt.Errorf("unable to start host networking: %q", err) } rtPath, err := getRuntimeDir() @@ -550,34 +574,46 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { files := []*os.File{dnr, dnw, dnw, fd} attr.Files = files logrus.Debug(v.CmdLine) - cmd := v.CmdLine + cmdLine := v.CmdLine // 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.IsLevelEnabled(logrus.DebugLevel) { - cmd = append(cmd, "-display", "none") + cmdLine = append(cmdLine, "-display", "none") } - _, err = os.StartProcess(v.CmdLine[0], cmd, attr) + stderrBuf := &bytes.Buffer{} + + cmd := &exec.Cmd{ + Args: cmdLine, + Path: cmdLine[0], + Stdin: dnr, + Stdout: dnw, + Stderr: stderrBuf, + ExtraFiles: []*os.File{fd}, + } + err = cmd.Start() if err != nil { // 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 + // look up 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) + cmdLine[0], err = cfg.FindHelperBinary(QemuCommand, true) if err != nil { return err } - _, err = os.StartProcess(cmd[0], cmd, attr) + cmd.Path = cmdLine[0] + err = cmd.Start() if err != nil { - return errors.Wrapf(err, "unable to execute %q", cmd) + return fmt.Errorf("unable to execute %q: %w", cmd, err) } } + defer cmd.Process.Release() //nolint:errcheck fmt.Println("Waiting for VM ...") socketPath, err := getRuntimeDir() if err != nil { @@ -592,6 +628,16 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if err == nil { break } + // check if qemu is still alive + var status syscall.WaitStatus + pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) + if err != nil { + return fmt.Errorf("failed to read qemu process status: %w", err) + } + if pid > 0 { + // child exited + return fmt.Errorf("qemu exited unexpectedly with exit code %d, stderr: %s", status.ExitStatus(), stderrBuf.String()) + } time.Sleep(wait) wait++ } @@ -682,7 +728,7 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, err } b, err := monitor.Run(input) if err != nil { - if errors.Cause(err) == os.ErrNotExist { + if errors.Is(err, os.ErrNotExist) { return machine.Stopped, nil } return "", err @@ -737,17 +783,17 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) { return nil } - pidString, err := v.PidFilePath.Read() + proxyPidString, err := v.PidFilePath.Read() if err != nil { return err } - pidNum, err := strconv.Atoi(string(pidString)) + proxyPid, err := strconv.Atoi(string(proxyPidString)) if err != nil { return err } - p, err := os.FindProcess(pidNum) - if p == nil && err != nil { + proxyProc, err := os.FindProcess(proxyPid) + if proxyProc == nil && err != nil { return err } @@ -756,7 +802,7 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { return err } // Kill the process - if err := p.Kill(); err != nil { + if err := proxyProc.Kill(); err != nil { return err } // Remove the pidfile @@ -770,24 +816,52 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { if err := qmpMonitor.Disconnect(); err != nil { // FIXME: this error should probably be returned - return nil // nolint: nilerr + return nil //nolint: nilerr } - disconnected = true - waitInternal := 250 * time.Millisecond - for i := 0; i < 5; i++ { - state, err := v.State(false) - if err != nil { - return err - } - if state != machine.Running { - break + + if err := v.ReadySocket.Delete(); err != nil { + return err + } + + if v.VMPidFilePath.GetPath() == "" { + // no vm pid file path means it's probably a machine created before we + // started using it, so we revert to the old way of waiting for the + // machine to stop + fmt.Println("Waiting for VM to stop running...") + waitInternal := 250 * time.Millisecond + for i := 0; i < 5; i++ { + state, err := v.State(false) + if err != nil { + return err + } + if state != machine.Running { + break + } + time.Sleep(waitInternal) + waitInternal *= 2 } - time.Sleep(waitInternal) - waitInternal *= 2 + // after the machine stops running it normally takes about 1 second for the + // qemu VM to exit so we wait a bit to try to avoid issues + time.Sleep(2 * time.Second) + return nil + } + + vmPidString, err := v.VMPidFilePath.Read() + if err != nil { + return err + } + vmPid, err := strconv.Atoi(strings.TrimSpace(string(vmPidString))) + if err != nil { + return err } - return v.ReadySocket.Delete() + fmt.Println("Waiting for VM to exit...") + for isProcessAlive(vmPid) { + time.Sleep(500 * time.Millisecond) + } + + return nil } // NewQMPMonitor creates the monitor subsection of our vm @@ -831,8 +905,14 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() if err != nil { return "", nil, err } - if state == machine.Running && !opts.Force { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + if state == machine.Running { + if !opts.Force { + return "", nil, fmt.Errorf("running vm %q cannot be destroyed", v.Name) + } + err := v.Stop(v.Name, machine.StopOptions{}) + if err != nil { + return "", nil, err + } } // Collect all the files that need to be destroyed @@ -874,8 +954,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() // remove socket and pid file if any: warn at low priority if things fail // Remove the pidfile + if err := v.VMPidFilePath.Delete(); err != nil { + logrus.Debugf("Error while removing VM pidfile: %v", err) + } if err := v.PidFilePath.Delete(); err != nil { - logrus.Debugf("Error while removing pidfile: %v", err) + logrus.Debugf("Error while removing proxy pidfile: %v", err) } // Remove socket if err := v.QMPMonitor.Address.Delete(); err != nil { @@ -904,11 +987,16 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) { } // Check if we can dial it if v.Starting && !bypass { - return "", nil + return machine.Starting, nil } monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { - // FIXME: this error should probably be returned + // If an improper cleanup was done and the socketmonitor was not deleted, + // it can appear as though the machine state is not stopped. Check for ECONNREFUSED + // almost assures us that the vm is stopped. + if errors.Is(err, syscall.ECONNREFUSED) { + return machine.Stopped, nil + } return "", err } if err := monitor.Connect(); err != nil { @@ -941,7 +1029,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { return err } if state != machine.Running { - return errors.Errorf("vm %q is not running", v.Name) + return fmt.Errorf("vm %q is not running", v.Name) } username := opts.Username @@ -952,7 +1040,8 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { sshDestination := username + "@localhost" port := strconv.Itoa(v.Port) - args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"} + args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR", "-o", "SetEnv=LC_ALL="} if len(opts.Args) > 0 { args = append(args, opts.Args...) } else { @@ -1051,6 +1140,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { listEntry.RemoteUsername = vm.RemoteUsername listEntry.IdentityPath = vm.IdentityPath listEntry.CreatedAt = vm.Created + listEntry.Starting = vm.Starting if listEntry.CreatedAt.IsZero() { listEntry.CreatedAt = time.Now() @@ -1064,6 +1154,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { if err != nil { return err } + listEntry.Running = state == machine.Running if !vm.LastUp.IsZero() { // this means we have already written a time to the config listEntry.LastUp = vm.LastUp @@ -1074,9 +1165,6 @@ func getVMInfos() ([]*machine.ListResponse, error) { return err } } - if state == machine.Running { - listEntry.Running = true - } listed = append(listed, listEntry) } @@ -1105,10 +1193,10 @@ func (p *Provider) IsValidVMName(name string) (bool, error) { func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { vms, err := getVMInfos() if err != nil { - return false, "", errors.Wrap(err, "error checking VM active") + return false, "", fmt.Errorf("error checking VM active: %w", err) } for _, vm := range vms { - if vm.Running { + if vm.Running || vm.Starting { return true, vm.Name, nil } } @@ -1116,7 +1204,7 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { } // startHostNetworking runs a binary on the host system that allows users -// to setup port forwarding to the podman virtual machine +// to set up port forwarding to the podman virtual machine func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { cfg, err := config.Default() if err != nil { @@ -1157,7 +1245,10 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { fmt.Println(cmd) } _, err = os.StartProcess(cmd[0], cmd, attr) - return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd) + if err != nil { + return "", 0, fmt.Errorf("unable to execute: %q: %w", cmd, err) + } + return forwardSock, state, nil } func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { @@ -1283,13 +1374,19 @@ func (v *MachineVM) setPIDSocket() error { if !rootless.IsRootless() { rtPath = "/run" } - pidFileName := fmt.Sprintf("%s.pid", v.Name) socketDir := filepath.Join(rtPath, "podman") - pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) + vmPidFileName := fmt.Sprintf("%s_vm.pid", v.Name) + proxyPidFileName := fmt.Sprintf("%s_proxy.pid", v.Name) + vmPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, vmPidFileName), &vmPidFileName) + if err != nil { + return err + } + proxyPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, proxyPidFileName), &proxyPidFileName) if err != nil { return err } - v.PidFilePath = *pidFilePath + v.VMPidFilePath = *vmPidFilePath + v.PidFilePath = *proxyPidFilePath return nil } @@ -1420,7 +1517,7 @@ func (v *MachineVM) update() error { b, err := v.ConfigPath.Read() if err != nil { if errors.Is(err, os.ErrNotExist) { - return errors.Wrap(machine.ErrNoSuchVM, v.Name) + return fmt.Errorf("%v: %w", v.Name, machine.ErrNoSuchVM) } return err } @@ -1471,16 +1568,22 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { if err != nil { return nil, err } - + connInfo := new(machine.ConnectionConfig) + podmanSocket, err := v.forwardSocketPath() + if err != nil { + return nil, err + } + connInfo.PodmanSocket = podmanSocket return &machine.InspectInfo{ - ConfigPath: v.ConfigPath, - Created: v.Created, - Image: v.ImageConfig, - LastUp: v.LastUp, - Name: v.Name, - Resources: v.ResourceConfig, - SSHConfig: v.SSHConfig, - State: state, + ConfigPath: v.ConfigPath, + ConnectionInfo: *connInfo, + Created: v.Created, + Image: v.ImageConfig, + LastUp: v.LastUp, + Name: v.Name, + Resources: v.ResourceConfig, + SSHConfig: v.SSHConfig, + State: state, }, nil } @@ -1490,7 +1593,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { // only if the virtualdisk size is less than // the given disk size if diskSize < oldSize { - return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) + return fmt.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) } // Find the qemu executable @@ -1506,7 +1609,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { resize.Stdout = os.Stdout resize.Stderr = os.Stderr if err := resize.Run(); err != nil { - return errors.Errorf("resizing image: %q", err) + return fmt.Errorf("resizing image: %q", err) } return nil @@ -1545,7 +1648,7 @@ func (v *MachineVM) editCmdLine(flag string, value string) { } } -// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine func (p *Provider) RemoveAndCleanMachines() error { var ( vm machine.VM @@ -1620,3 +1723,15 @@ func (p *Provider) RemoveAndCleanMachines() error { } return prevErr } + +func isProcessAlive(pid int) bool { + err := unix.Kill(pid, syscall.Signal(0)) + if err == nil || err == unix.EPERM { + return true + } + return false +} + +func (p *Provider) VMType() string { + return vmtype +} diff --git a/pkg/machine/qemu/machine_test.go b/pkg/machine/qemu/machine_test.go index 62ca6068a..4c393d0f4 100644 --- a/pkg/machine/qemu/machine_test.go +++ b/pkg/machine/qemu/machine_test.go @@ -1,3 +1,6 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) +// +build amd64,!windows arm64,!windows + package qemu import ( diff --git a/pkg/machine/qemu/options_darwin_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go index 4c954af00..d75237938 100644 --- a/pkg/machine/qemu/options_darwin_arm64.go +++ b/pkg/machine/qemu/options_darwin_arm64.go @@ -4,6 +4,8 @@ import ( "os" "os/exec" "path/filepath" + + "github.com/containers/common/pkg/config" ) var ( @@ -15,8 +17,8 @@ func (v *MachineVM) addArchOptions() []string { opts := []string{ "-accel", "hvf", "-accel", "tcg", - "-cpu", "cortex-a57", - "-M", "virt,highmem=off", + "-cpu", "host", + "-M", "virt,highmem=on", "-drive", "file=" + getEdk2CodeFd("edk2-aarch64-code.fd") + ",if=pflash,format=raw,readonly=on", "-drive", "file=" + ovmfDir + ",if=pflash,format=raw"} return opts @@ -38,6 +40,22 @@ func getOvmfDir(imagePath, vmName string) string { } /* + * When QEmu is installed in a non-default location in the system + * we can use the qemu-system-* binary path to figure the install + * location for Qemu and use it to look for edk2-code-fd + */ +func getEdk2CodeFdPathFromQemuBinaryPath() string { + cfg, err := config.Default() + if err == nil { + execPath, err := cfg.FindHelperBinary(QemuCommand, true) + if err == nil { + return filepath.Clean(filepath.Join(filepath.Dir(execPath), "..", "share", "qemu")) + } + } + return "" +} + +/* * QEmu can be installed in multiple locations on MacOS, especially on * Apple Silicon systems. A build from source will likely install it in * /usr/local/bin, whereas Homebrew package management standard is to @@ -45,6 +63,7 @@ func getOvmfDir(imagePath, vmName string) string { */ func getEdk2CodeFd(name string) string { dirs := []string{ + getEdk2CodeFdPathFromQemuBinaryPath(), "/opt/homebrew/opt/podman/libexec/share/qemu", "/usr/local/share/qemu", "/opt/homebrew/share/qemu", diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 0b2874baf..450d8b877 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -6,6 +6,7 @@ package wsl import ( "bufio" "encoding/json" + "errors" "fmt" "io" "io/fs" @@ -18,10 +19,10 @@ import ( "strings" "time" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/homedir" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" @@ -116,6 +117,43 @@ ln -fs /home/[USER]/.config/systemd/[USER]/linger-example.service \ /home/[USER]/.config/systemd/[USER]/default.target.wants/linger-example.service ` +const proxyConfigSetup = `#!/bin/bash + +SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf +ENVD_CONF=/etc/environment.d/default-env.conf +PROFILE_CONF=/etc/profile.d/default-env.sh + +IFS="|" +read proxies + +mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/ +rm -f $SYSTEMD_CONF +for proxy in $proxies; do + output+="$proxy " +done +echo "[Manager]" >> $SYSTEMD_CONF +echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF + +echo $output >> $SYSTEMD_CONF +rm -f $ENVD_CONF +for proxy in $proxies; do + echo "$proxy" >> $ENVD_CONF +done +rm -f $PROFILE_CONF +for proxy in $proxies; do + echo "export $proxy" >> $PROFILE_CONF +done +` + +const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \ +then /usr/local/bin/proxyinit; \ +else exit 42; \ +fi` + +const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \ + /etc/environment.d/default-env.conf \ + /etc/profile.d/default-env.sh` + const wslInstallError = `Could not %s. See previous output for any potential failure details. If you can not resolve the issue, and rerunning fails, try the "wsl --install" process outlined in the following article: @@ -239,7 +277,7 @@ func readAndMigrate(configPath string, name string) (*MachineVM, error) { b, err := os.ReadFile(configPath) if err != nil { if errors.Is(err, os.ErrNotExist) { - return nil, errors.Wrap(machine.ErrNoSuchVM, name) + return nil, fmt.Errorf("%v: %w", name, machine.ErrNoSuchVM) } return vm, err } @@ -300,6 +338,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return cont, err } + _ = setupWslProxyEnv() homeDir := homedir.Get() sshDir := filepath.Join(homeDir, ".ssh") v.IdentityPath = filepath.Join(sshDir, v.Name) @@ -368,7 +407,7 @@ func (v *MachineVM) writeConfig() error { return err } if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil { - return errors.Wrap(err, "could not write machine json config") + return fmt.Errorf("could not write machine json config: %w", err) } return nil @@ -406,38 +445,38 @@ func provisionWSLDist(v *MachineVM) (string, error) { distDir := filepath.Join(vmDataDir, "wsldist") distTarget := filepath.Join(distDir, v.Name) if err := os.MkdirAll(distDir, 0755); err != nil { - return "", errors.Wrap(err, "could not create wsldist directory") + return "", fmt.Errorf("could not create wsldist directory: %w", err) } dist := toDist(v.Name) fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...") if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil { - return "", errors.Wrap(err, "WSL import of guest OS failed") + return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) } fmt.Println("Installing packages (this will take awhile)...") if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil { - return "", errors.Wrap(err, "package upgrade on guest OS failed") + return "", fmt.Errorf("package upgrade on guest OS failed: %w", err) } fmt.Println("Enabling Copr") if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil { - return "", errors.Wrap(err, "enabling copr failed") + return "", fmt.Errorf("enabling copr failed: %w", err) } fmt.Println("Enabling podman4 repo") if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil { - return "", errors.Wrap(err, "enabling copr failed") + return "", fmt.Errorf("enabling copr failed: %w", err) } if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil { - return "", errors.Wrap(err, "package installation on guest OS failed") + return "", fmt.Errorf("package installation on guest OS failed: %w", err) } // Fixes newuidmap if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil { - return "", errors.Wrap(err, "package reinstallation of shadow-utils on guest OS failed") + return "", fmt.Errorf("package reinstallation of shadow-utils on guest OS failed: %w", err) } // Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every @@ -456,28 +495,28 @@ func createKeys(v *MachineVM, dist string, sshDir string) error { user := v.RemoteUsername if err := os.MkdirAll(sshDir, 0700); err != nil { - return errors.Wrap(err, "could not create ssh directory") + return fmt.Errorf("could not create ssh directory: %w", err) } if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil { - return errors.Wrap(err, "could not cycle WSL dist") + return fmt.Errorf("could not cycle WSL dist: %w", err) } key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist) if err != nil { - return errors.Wrap(err, "could not create ssh keys") + return fmt.Errorf("could not create ssh keys: %w", err) } if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil { - return errors.Wrap(err, "could not create root authorized keys on guest OS") + return fmt.Errorf("could not create root authorized keys on guest OS: %w", err) } userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+ "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user) if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil { - return errors.Wrapf(err, "could not create '%s' authorized keys on guest OS", v.RemoteUsername) + return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", v.RemoteUsername, err) } return nil @@ -486,25 +525,25 @@ func createKeys(v *MachineVM, dist string, sshDir string) error { func configureSystem(v *MachineVM, dist string) error { user := v.RemoteUsername if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil { - return errors.Wrap(err, "could not configure SSH port for guest OS") + return fmt.Errorf("could not configure SSH port for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil { - return errors.Wrap(err, "could not configure systemd settings for guest OS") + return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil { - return errors.Wrap(err, "could not add wheel to sudoers") + return fmt.Errorf("could not add wheel to sudoers: %w", err) } if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c", "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil { - return errors.Wrap(err, "could not generate systemd-sysusers override for guest OS") + return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err) } lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user) if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil { - return errors.Wrap(err, "could not generate linger service for guest OS") + return fmt.Errorf("could not generate linger service for guest OS: %w", err) } if err := enableUserLinger(v, dist); err != nil { @@ -512,15 +551,49 @@ func configureSystem(v *MachineVM, dist string) error { } if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil { - return errors.Wrap(err, "could not configure systemd settomgs for guest OS") + return fmt.Errorf("could not configure systemd settomgs for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil { - return errors.Wrap(err, "could not create containers.conf for guest OS") + return fmt.Errorf("could not create containers.conf for guest OS: %w", err) } if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil { - return errors.Wrap(err, "could not create podman-machine file for guest OS") + return fmt.Errorf("could not create podman-machine file for guest OS: %w", err) + } + + return nil +} + +func configureProxy(dist string, useProxy bool) error { + if !useProxy { + _ = runCmdPassThrough("wsl", "-d", dist, "sh", "-c", clearProxySettings) + return nil + } + var content string + for i, key := range config.ProxyEnv { + if value, _ := os.LookupEnv(key); len(value) > 0 { + var suffix string + if i < (len(config.ProxyEnv) - 1) { + suffix = "|" + } + content = fmt.Sprintf("%s%s=\"%s\"%s", content, key, value, suffix) + } + } + + if err := pipeCmdPassThrough("wsl", content, "-d", dist, "sh", "-c", proxyConfigAttempt); err != nil { + const failMessage = "Failure creating proxy configuration" + if exitErr, isExit := err.(*exec.ExitError); isExit && exitErr.ExitCode() != 42 { + return fmt.Errorf("%v: %w", failMessage, err) + } + + fmt.Println("Installing proxy support") + _ = pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit") + + if err = pipeCmdPassThrough("wsl", content, "-d", dist, "/usr/local/bin/proxyinit"); err != nil { + return fmt.Errorf("%v: %w", failMessage, err) + } } return nil @@ -529,7 +602,7 @@ func configureSystem(v *MachineVM, dist string) error { func enableUserLinger(v *MachineVM, dist string) error { lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", lingerCmd); err != nil { - return errors.Wrap(err, "could not enable linger for remote user on guest OS") + return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err) } return nil @@ -538,21 +611,26 @@ func enableUserLinger(v *MachineVM, dist string) error { func installScripts(dist string) error { if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c", "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil { - return errors.Wrap(err, "could not create enterns script for guest OS") + return fmt.Errorf("could not create enterns script for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c", "cat > /etc/profile.d/enterns.sh"); err != nil { - return errors.Wrap(err, "could not create motd profile script for guest OS") + return fmt.Errorf("could not create motd profile script for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil { - return errors.Wrap(err, "could not create a WSL MOTD for guest OS") + return fmt.Errorf("could not create a WSL MOTD for guest OS: %w", err) } if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c", "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil { - return errors.Wrap(err, "could not create bootstrap script for guest OS") + return fmt.Errorf("could not create bootstrap script for guest OS: %w", err) + } + + if err := pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit"); err != nil { + return fmt.Errorf("could not create proxyinit script for guest OS: %w", err) } return nil @@ -595,10 +673,10 @@ func checkAndInstallWSL(opts machine.InitOptions) (bool, error) { func attemptFeatureInstall(opts machine.InitOptions, admin bool) error { if !winVersionAtLeast(10, 0, 18362) { - return errors.Errorf("Your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later") + return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later") } else if !winVersionAtLeast(10, 0, 19041) { fmt.Fprint(os.Stderr, wslOldVersion) - return errors.Errorf("WSL can not be automatically installed") + return errors.New("the WSL can not be automatically installed") } message := "WSL is not installed on this system, installing it.\n\n" @@ -612,7 +690,7 @@ func attemptFeatureInstall(opts machine.InitOptions, admin bool) error { "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command." if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 { - return errors.Errorf("WSL installation aborted") + return errors.New("the WSL installation aborted") } if !opts.ReExec && !admin { @@ -648,12 +726,12 @@ func installWsl() error { defer log.Close() if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature", "/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) { - return errors.Wrap(err, "could not enable WSL Feature") + return fmt.Errorf("could not enable WSL Feature: %w", err) } if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature", "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) { - return errors.Wrap(err, "could not enable Virtual Machine Feature") + return fmt.Errorf("could not enable Virtual Machine Feature: %w", err) } log.Close() @@ -687,7 +765,7 @@ func installWslKernel() error { } if err != nil { - return errors.Wrap(err, "could not install WSL Kernel") + return fmt.Errorf("could not install WSL Kernel: %w", err) } return nil @@ -816,6 +894,26 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error { return cmd.Run() } +func setupWslProxyEnv() (hasProxy bool) { + current, _ := os.LookupEnv("WSLENV") + for _, key := range config.ProxyEnv { + if value, _ := os.LookupEnv(key); len(value) < 1 { + continue + } + + hasProxy = true + delim := "" + if len(current) > 0 { + delim = ":" + } + current = fmt.Sprintf("%s%s%s/u", current, delim, key) + } + if hasProxy { + os.Setenv("WSLENV", current) + } + return +} + func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { // If one setting fails to be applied, the others settings will not fail and still be applied. // The setting(s) that failed to be applied will have its errors returned in setErrors @@ -824,23 +922,23 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if opts.Rootful != nil && v.Rootful != *opts.Rootful { err := v.setRootful(*opts.Rootful) if err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "error setting rootful option")) + setErrors = append(setErrors, fmt.Errorf("error setting rootful option: %w", err)) } else { v.Rootful = *opts.Rootful } } if opts.CPUs != nil { - setErrors = append(setErrors, errors.Errorf("changing CPUs not suppored for WSL machines")) + setErrors = append(setErrors, errors.New("changing CPUs not supported for WSL machines")) } if opts.Memory != nil { - setErrors = append(setErrors, errors.Errorf("changing memory not suppored for WSL machines")) + setErrors = append(setErrors, errors.New("changing memory not supported for WSL machines")) } if opts.DiskSize != nil { - setErrors = append(setErrors, errors.Errorf("changing Disk Size not suppored for WSL machines")) + setErrors = append(setErrors, errors.New("changing Disk Size not supported for WSL machines")) } return setErrors, v.writeConfig() @@ -848,14 +946,18 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if v.isRunning() { - return errors.Errorf("%q is already running", name) + return fmt.Errorf("%q is already running", name) } dist := toDist(name) + useProxy := setupWslProxyEnv() + if err := configureProxy(dist, useProxy); err != nil { + return err + } err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap") if err != nil { - return errors.Wrap(err, "WSL bootstrap script failed") + return fmt.Errorf("the WSL bootstrap script failed: %w", err) } if !v.Rootful { @@ -897,7 +999,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { func launchWinProxy(v *MachineVM) (bool, string, error) { machinePipe := toDist(v.Name) if !pipeAvailable(machinePipe) { - return false, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe) + return false, "", fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe) } globalName := false @@ -945,7 +1047,7 @@ func launchWinProxy(v *MachineVM) (bool, string, 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) + return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode) } return nil @@ -1083,7 +1185,7 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { } if !wsl || !sysd { - return errors.Errorf("%q is not running", v.Name) + return fmt.Errorf("%q is not running", v.Name) } _, _, _ = v.updateTimeStamps(true) @@ -1095,12 +1197,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { cmd := exec.Command("wsl", "-d", dist, "sh") cmd.Stdin = strings.NewReader(waitTerm) if err = cmd.Start(); err != nil { - return errors.Wrap(err, "Error executing wait command") + return fmt.Errorf("executing wait command: %w", err) } exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0") if err = exitCmd.Run(); err != nil { - return errors.Wrap(err, "Error stopping sysd") + return fmt.Errorf("stopping sysd: %w", err) } if err = cmd.Wait(); err != nil { @@ -1181,7 +1283,7 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun var files []string if v.isRunning() { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + return "", nil, fmt.Errorf("running vm %q cannot be destroyed", v.Name) } // Collect all the files that need to be destroyed @@ -1253,7 +1355,7 @@ func (v *MachineVM) isRunning() bool { // Added ssh function to VM interface: pkg/machine/config/go : line 58 func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { if !v.isRunning() { - return errors.Errorf("vm %q is not running.", v.Name) + return fmt.Errorf("vm %q is not running.", v.Name) } username := opts.Username @@ -1312,6 +1414,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) { listEntry.RemoteUsername = vm.RemoteUsername listEntry.Port = vm.Port listEntry.IdentityPath = vm.IdentityPath + listEntry.Starting = false running := vm.isRunning() listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running) @@ -1477,7 +1580,7 @@ func (v *MachineVM) getResources() (resources machine.ResourceConfig) { return } -// RemoveAndCleanMachines removes all machine and cleans up any other files associatied with podman machine +// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine func (p *Provider) RemoveAndCleanMachines() error { var ( vm machine.VM @@ -1552,3 +1655,7 @@ func (p *Provider) RemoveAndCleanMachines() error { } return prevErr } + +func (p *Provider) VMType() string { + return vmtype +} diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go index b5c28e015..43f54fdd4 100644 --- a/pkg/machine/wsl/util_windows.go +++ b/pkg/machine/wsl/util_windows.go @@ -2,6 +2,7 @@ package wsl import ( "encoding/base64" + "errors" "fmt" "io/ioutil" "os" @@ -11,7 +12,6 @@ import ( "unicode/utf16" "unsafe" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" @@ -157,9 +157,9 @@ func relaunchElevatedWait() error { case syscall.WAIT_OBJECT_0: break case syscall.WAIT_FAILED: - return errors.Wrap(err, "could not wait for process, failed") + return fmt.Errorf("could not wait for process, failed: %w", err) default: - return errors.Errorf("could not wait for process, unknown error") + return errors.New("could not wait for process, unknown error") } var code uint32 if err := syscall.GetExitCodeProcess(handle, &code); err != nil { @@ -174,7 +174,7 @@ func relaunchElevatedWait() error { func wrapMaybe(err error, message string) error { if err != nil { - return errors.Wrap(err, message) + return fmt.Errorf("%v: %w", message, err) } return errors.New(message) @@ -182,10 +182,10 @@ func wrapMaybe(err error, message string) error { func wrapMaybef(err error, format string, args ...interface{}) error { if err != nil { - return errors.Wrapf(err, format, args...) + return fmt.Errorf(format+": %w", append(args, err)...) } - return errors.Errorf(format, args...) + return fmt.Errorf(format, args...) } func reboot() error { @@ -202,14 +202,14 @@ func reboot() error { dataDir, err := homedir.GetDataHome() if err != nil { - return errors.Wrap(err, "could not determine data directory") + return fmt.Errorf("could not determine data directory: %w", err) } if err := os.MkdirAll(dataDir, 0755); err != nil { - return errors.Wrap(err, "could not create data directory") + return fmt.Errorf("could not create data directory: %w", err) } commFile := filepath.Join(dataDir, "podman-relaunch.dat") if err := ioutil.WriteFile(commFile, []byte(encoded), 0600); err != nil { - return errors.Wrap(err, "could not serialize command state") + return fmt.Errorf("could not serialize command state: %w", err) } command := fmt.Sprintf(pShellLaunch, commFile) @@ -244,7 +244,7 @@ func reboot() error { procExit := user32.NewProc("ExitWindowsEx") if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG, SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 { - return errors.Wrap(err, "reboot failed") + return fmt.Errorf("reboot failed: %w", err) } return nil @@ -262,19 +262,19 @@ func obtainShutdownPrivilege() error { var hToken uintptr if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 { - return errors.Wrap(err, "error opening process token") + return fmt.Errorf("error opening process token: %w", err) } var privs TokenPrivileges if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 { - return errors.Wrap(err, "error looking up shutdown privilege") + return fmt.Errorf("error looking up shutdown privilege: %w", err) } privs.privilegeCount = 1 privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 { - return errors.Wrap(err, "error enabling shutdown privilege on token") + return fmt.Errorf("error enabling shutdown privilege on token: %w", err) } return nil @@ -295,13 +295,13 @@ func getProcessState(pid int) (active bool, exitCode int) { func addRunOnceRegistryEntry(command string) error { k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE) if err != nil { - return errors.Wrap(err, "could not open RunOnce registry entry") + return fmt.Errorf("could not open RunOnce registry entry: %w", err) } defer k.Close() if err := k.SetExpandStringValue("podman-machine", command); err != nil { - return errors.Wrap(err, "could not open RunOnce registry entry") + return fmt.Errorf("could not open RunOnce registry entry: %w", err) } return nil |