diff --git a/cmd/podman/machine/reset.go b/cmd/podman/machine/reset.go new file mode 100644 index 0000000000..920b553a1f --- /dev/null +++ b/cmd/podman/machine/reset.go @@ -0,0 +1,98 @@ +//go:build amd64 || arm64 + +package machine + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/cmd/podman/validate" + "github.com/containers/podman/v5/pkg/machine" + "github.com/containers/podman/v5/pkg/machine/shim" + "github.com/containers/podman/v5/pkg/machine/vmconfigs" + "github.com/spf13/cobra" +) + +var ( + resetCmd = &cobra.Command{ + Use: "reset [options]", + Short: "Remove all machines", + Long: "Remove all machines, configurations, data, and cached images", + PersistentPreRunE: machinePreRunE, + RunE: reset, + Args: validate.NoArgs, + Example: `podman machine reset`, + ValidArgsFunction: completion.AutocompleteNone, + } +) + +var ( + resetOptions machine.ResetOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: resetCmd, + Parent: machineCmd, + }) + + flags := resetCmd.Flags() + formatFlagName := "force" + flags.BoolVarP(&resetOptions.Force, formatFlagName, "f", false, "Do not prompt before reset") +} + +func reset(_ *cobra.Command, _ []string) error { + var ( + err error + ) + + dirs, err := machine.GetMachineDirs(provider.VMType()) + if err != nil { + return err + } + + // TODO we could consider saying we get a list of vms but can proceed + // to just delete all local disk dirs, etc. Maybe a --proceed? + mcs, err := vmconfigs.LoadMachinesInDir(dirs) + if err != nil { + return err + } + + if !resetOptions.Force { + vms := vmNamesFromMcs(mcs) + resetConfirmationMessage(vms) + reader := bufio.NewReader(os.Stdin) + fmt.Print("\nAre you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return err + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + + // resetErr can be nil or a multi-error + return shim.Reset(dirs, provider, mcs) +} + +func resetConfirmationMessage(vms []string) { + fmt.Println("Warning: this command will delete all existing Podman machines") + fmt.Println("and all of the configuration and data directories for Podman machines") + fmt.Printf("\nThe following machine(s) will be deleted:\n\n") + for _, msg := range vms { + fmt.Printf("%s\n", msg) + } +} + +func vmNamesFromMcs(mcs map[string]*vmconfigs.MachineConfig) []string { + keys := make([]string, 0, len(mcs)) + for k := range mcs { + keys = append(keys, k) + } + return keys +} diff --git a/docs/source/markdown/podman-machine-reset.1.md b/docs/source/markdown/podman-machine-reset.1.md new file mode 100644 index 0000000000..7bb92d7896 --- /dev/null +++ b/docs/source/markdown/podman-machine-reset.1.md @@ -0,0 +1,47 @@ +% podman-machine-reset 1 + +## NAME +podman\-machine\-reset - Reset Podman machines and environment + +## SYNOPSIS +**podman machine reset** [*options*] + +## DESCRIPTION + +Reset your Podman machine environment. This command stops any running machines +and then removes them. Configuration and data files are then removed. Data files +would include machine disk images and any previously pulled cache images. When +this command is run, all of your Podman machines will have been deleted. + +## OPTIONS + +#### **--force**, **-f** + +Reset without confirmation. + +#### **--help** + +Print usage statement. + + +## EXAMPLES + +``` +$ podman machine reset +Warning: this command will delete all existing podman machines +and all of the configuration and data directories for Podman machines + +The following machine(s) will be deleted: + +dev +podman-machine-default + +Are you sure you want to continue? [y/N] y +$ +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)** + +## HISTORY +Feb 2024, Originally compiled by Brent Baude diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index e42eb527db..ee069c9968 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -22,21 +22,22 @@ environment variable while the machines are running can lead to unexpected behav ## SUBCOMMANDS -| Command | Man Page | Description | -|---------|-----------------------------------------------------------|--------------------------------------| -| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info | -| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | -| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines | -| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | -| os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS | -| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | -| set | [podman-machine-set(1)](podman-machine-set.1.md) | Set a virtual machine setting | -| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | -| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | -| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | +| Command | Man Page | Description | +|---------|----------------------------------------------------------|---------------------------------------| +| info | [podman-machine-info(1)](podman-machine-info.1.md) | Display machine host info | +| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | +| inspect | [podman-machine-inspect(1)](podman-machine-inspect.1.md) | Inspect one or more virtual machines | +| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | +| os | [podman-machine-os(1)](podman-machine-os.1.md) | Manage a Podman virtual machine's OS | +| reset | [podman-machine-reset(1)](podman-machine-reset.1.md) | Reset Podman machines and environment | +| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | +| set | [podman-machine-set(1)](podman-machine-set.1.md) | Set a virtual machine setting | +| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | +| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | +| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)** +**[podman(1)](podman.1.md)**, **[podman-machine-info(1)](podman-machine-info.1.md)**, **[podman-machine-init(1)](podman-machine-init.1.md)**, **[podman-machine-list(1)](podman-machine-list.1.md)**, **[podman-machine-os(1)](podman-machine-os.1.md)**, **[podman-machine-rm(1)](podman-machine-rm.1.md)**, **[podman-machine-ssh(1)](podman-machine-ssh.1.md)**, **[podman-machine-start(1)](podman-machine-start.1.md)**, **[podman-machine-stop(1)](podman-machine-stop.1.md)**, **[podman-machine-inspect(1)](podman-machine-inspect.1.md)**, **[podman-machine-reset(1)](podman-machine-reset.1.md)** ## HISTORY March 2021, Originally compiled by Ashley Cui diff --git a/pkg/machine/config.go b/pkg/machine/config.go index dc6b2d1a70..650443e82f 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -83,6 +83,10 @@ type RemoveOptions struct { SaveIgnition bool } +type ResetOptions struct { + Force bool +} + type InspectOptions struct{} // TODO This can be removed when WSL is refactored into podman 5 diff --git a/pkg/machine/e2e/config_reset_test.go b/pkg/machine/e2e/config_reset_test.go new file mode 100644 index 0000000000..2bba5b92b4 --- /dev/null +++ b/pkg/machine/e2e/config_reset_test.go @@ -0,0 +1,25 @@ +package e2e_test + +type resetMachine struct { + /* + -f, --force Stop and do not prompt before reseting + */ + + force bool + + cmd []string +} + +func (i *resetMachine) buildCmd(m *machineTestBuilder) []string { + cmd := []string{"machine", "reset"} + if i.force { + cmd = append(cmd, "--force") + } + i.cmd = cmd + return cmd +} + +func (i *resetMachine) withForce() *resetMachine { + i.force = true + return i +} diff --git a/pkg/machine/e2e/reset_test.go b/pkg/machine/e2e/reset_test.go new file mode 100644 index 0000000000..655a00efdd --- /dev/null +++ b/pkg/machine/e2e/reset_test.go @@ -0,0 +1,88 @@ +package e2e_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman machine reset", func() { + var ( + mb *machineTestBuilder + testDir string + ) + + BeforeEach(func() { + testDir, mb = setup() + }) + AfterEach(func() { + teardown(originalHomeDir, testDir, mb) + }) + + It("starting from scratch should not error", func() { + i := resetMachine{} + session, err := mb.setCmd(i.withForce()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + }) + + It("reset machine with one defined machine", func() { + name := randomString() + i := new(initMachine) + session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + ls := new(listMachine) + beforeSession, err := mb.setCmd(ls.withNoHeading()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(beforeSession).To(Exit(0)) + Expect(beforeSession.outputToStringSlice()).To(HaveLen(1)) + + reset := resetMachine{} + resetSession, err := mb.setCmd(reset.withForce()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(resetSession).To(Exit(0)) + + afterSession, err := mb.setCmd(ls.withNoHeading()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(afterSession).To(Exit(0)) + Expect(afterSession.outputToStringSlice()).To(BeEmpty()) + }) + + It("reset with running machine and other machines idle ", func() { + name := randomString() + i := new(initMachine) + session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session).To(Exit(0)) + + ls := new(listMachine) + beforeSession, err := mb.setCmd(ls.withNoHeading()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(beforeSession).To(Exit(0)) + Expect(beforeSession.outputToStringSlice()).To(HaveLen(1)) + + name2 := randomString() + i2 := new(initMachine) + session2, err := mb.setName(name2).setCmd(i2.withImagePath(mb.imagePath)).run() + Expect(err).ToNot(HaveOccurred()) + Expect(session2).To(Exit(0)) + + beforeSession, err = mb.setCmd(ls.withNoHeading()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(beforeSession).To(Exit(0)) + Expect(beforeSession.outputToStringSlice()).To(HaveLen(2)) + + reset := resetMachine{} + resetSession, err := mb.setCmd(reset.withForce()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(resetSession).To(Exit(0)) + + afterSession, err := mb.setCmd(ls.withNoHeading()).run() + Expect(err).ToNot(HaveOccurred()) + Expect(afterSession).To(Exit(0)) + Expect(afterSession.outputToStringSlice()).To(BeEmpty()) + }) + +}) diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go index f4de604711..03a979af2d 100644 --- a/pkg/machine/shim/host.go +++ b/pkg/machine/shim/host.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "runtime" "time" @@ -12,6 +13,8 @@ import ( machineDefine "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/ignition" "github.com/containers/podman/v5/pkg/machine/vmconfigs" + "github.com/containers/podman/v5/utils" + "github.com/hashicorp/go-multierror" "github.com/sirupsen/logrus" ) @@ -446,3 +449,39 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefin return nil } + +func Reset(dirs *machineDefine.MachineDirs, mp vmconfigs.VMProvider, mcs map[string]*vmconfigs.MachineConfig) error { + var resetErrors *multierror.Error + for _, mc := range mcs { + err := Stop(mc, mp, dirs, true) + if err != nil { + resetErrors = multierror.Append(resetErrors, err) + } + _, genericRm, err := mc.Remove(false, false) + if err != nil { + resetErrors = multierror.Append(resetErrors, err) + } + _, providerRm, err := mp.Remove(mc) + if err != nil { + resetErrors = multierror.Append(resetErrors, err) + } + + if err := genericRm(); err != nil { + resetErrors = multierror.Append(resetErrors, err) + } + if err := providerRm(); err != nil { + resetErrors = multierror.Append(resetErrors, err) + } + } + + // Delete the various directories + // Note: we cannot delete the machine run dir blindly like this because + // other things live there like the podman.socket and so forth. + + // in linux this ~/.local/share/containers/podman/machine + dataDirErr := utils.GuardedRemoveAll(filepath.Dir(dirs.DataDir.GetPath())) + // in linux this ~/.config/containers/podman/machine + confDirErr := utils.GuardedRemoveAll(filepath.Dir(dirs.ConfigDir.GetPath())) + resetErrors = multierror.Append(resetErrors, confDirErr, dataDirErr) + return resetErrors.ErrorOrNil() +}