From 79bf5010eda37c07f577414ae71ecb8f8c8714a2 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 22 Nov 2019 11:39:49 -0500 Subject: Add podman system reset command This command will destroy all data created via podman. It will remove containers, images, volumes, pods. Signed-off-by: Daniel J Walsh --- API.md | 8 ++ cmd/podman/cliconfig/config.go | 5 ++ cmd/podman/reset.go | 71 +++++++++++++++++ cmd/podman/system.go | 1 + cmd/podman/varlink/io.podman.varlink | 4 + commands.md | 6 ++ completions/bash/podman | 16 ++++ docs/source/markdown/podman-system-reset.1.md | 25 ++++++ docs/source/markdown/podman-system.1.md | 5 +- libpod/reset.go | 107 ++++++++++++++++++++++++++ libpod/runtime_migrate_unsupported.go | 4 + pkg/adapter/reset.go | 13 ++++ pkg/adapter/reset_remote.go | 12 +++ pkg/varlinkapi/system.go | 22 +++++- test/e2e/libpod_suite_test.go | 4 + test/e2e/system_reset_test.go | 83 ++++++++++++++++++++ 16 files changed, 383 insertions(+), 3 deletions(-) create mode 100644 cmd/podman/reset.go create mode 100644 docs/source/markdown/podman-system-reset.1.md create mode 100644 libpod/reset.go create mode 100644 pkg/adapter/reset.go create mode 100644 pkg/adapter/reset_remote.go create mode 100644 test/e2e/system_reset_test.go diff --git a/API.md b/API.md index a02a8cf6f..3a66db83b 100755 --- a/API.md +++ b/API.md @@ -149,6 +149,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func RemovePod(name: string, force: bool) string](#RemovePod) +[func Reset() ](#Reset) + [func RestartContainer(name: string, timeout: int) string](#RestartContainer) [func RestartPod(name: string) string](#RestartPod) @@ -1059,6 +1061,12 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.RemovePod '{"name": "62f4 "pod": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" } ~~~ +### func Reset +
+ +method Reset()
+Reset resets Podman back to its initial state. +Removes all Pods, Containers, Images and Volumes ### func RestartContainer
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index a34afa827..df7ea6c33 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -658,6 +658,11 @@ type SystemPruneValues struct { Volume bool } +type SystemResetValues struct { + PodmanCommand + Force bool +} + type SystemRenumberValues struct { PodmanCommand } diff --git a/cmd/podman/reset.go b/cmd/podman/reset.go new file mode 100644 index 000000000..9d16dc978 --- /dev/null +++ b/cmd/podman/reset.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + systemResetCommand cliconfig.SystemResetValues + systemResetDescription = `Reset podman storage back to default state" + + All containers will be stopped and removed, and all images, volumes and container content will be removed. +` + _systemResetCommand = &cobra.Command{ + Use: "reset", + Args: noSubArgs, + Short: "Reset podman storage", + Long: systemResetDescription, + RunE: func(cmd *cobra.Command, args []string) error { + systemResetCommand.InputArgs = args + systemResetCommand.GlobalFlags = MainGlobalOpts + systemResetCommand.Remote = remoteclient + return systemResetCmd(&systemResetCommand) + }, + } +) + +func init() { + systemResetCommand.Command = _systemResetCommand + flags := systemResetCommand.Flags() + flags.BoolVarP(&systemResetCommand.Force, "force", "f", false, "Do not prompt for confirmation") + + systemResetCommand.SetHelpTemplate(HelpTemplate()) + systemResetCommand.SetUsageTemplate(UsageTemplate()) +} + +func systemResetCmd(c *cliconfig.SystemResetValues) error { + // Prompt for confirmation if --force is not set + if !c.Force { + reader := bufio.NewReader(os.Stdin) + fmt.Print(` +WARNING! This will remove: + - all containers + - all pods + - all images + - all build cache +Are you sure you want to continue? [y/N] `) + ans, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(ans)[0] != 'y' { + return nil + } + } + + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + // No shutdown, since storage will be destroyed when command completes + + return runtime.Reset() +} diff --git a/cmd/podman/system.go b/cmd/podman/system.go index 80080bf44..921d0c037 100644 --- a/cmd/podman/system.go +++ b/cmd/podman/system.go @@ -19,6 +19,7 @@ var ( ) var systemCommands = []*cobra.Command{ + _systemResetCommand, _infoCommand, _pruneSystemCommand, } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index e76b9627e..a3fd27ed6 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -536,6 +536,10 @@ method GetVersion() -> ( remote_api_version: int ) +# Reset resets Podman back to its initial state. +# Removes all Pods, Containers, Images and Volumes +method Reset() -> () + # GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, # build information of Podman, and system-wide registries. method GetInfo() -> (info: PodmanInfo) diff --git a/commands.md b/commands.md index aae5d7e02..de9169a4b 100644 --- a/commands.md +++ b/commands.md @@ -78,6 +78,12 @@ | [podman-stats(1)](/docs/source/markdown/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics | | [podman-stop(1)](/docs/source/markdown/podman-stop.1.md) | Stops one or more running containers | | [podman-system(1)](/docs/source/markdown/podman-system.1.md) | Manage podman | +| [podman-system-df(1)](/docs/source/markdown/podman-system-df.1.md) | Show podman disk usage. | +| [podman-system-info(1)](/docs/source/markdown/podman-info.1.md) | Displays Podman related system information. | +| [podman-system-migrate(1)](/docs/source/markdown/podman-system-migrate.1.md) | Migrate existing containers to a new podman version. | +| [podman-system-prune(1)](/docs/source/markdown/podman-system-prune.1.md) | Remove all unused container, image and volume data. | +| [podman-system-renumber(1)](/docs/source/markdown/podman-system-renumber.1.md) | Migrate lock numbers to handle a change in maximum number of locks. | +| [podman-system-reset(1)](/docs/source/markdown/podman-system-reset.1.md) | Reset storage back to original state. Remove all pods, containers, images, volumes. | | [podman-tag(1)](/docs/source/markdown/podman-tag.1.md) | Add an additional name to a local image | [![...](/docs/source/markdown/play.png)](https://asciinema.org/a/133803) | | [podman-top(1)](/docs/source/markdown/podman-top.1.md) | Display the running processes of a container | | [podman-umount(1)](/docs/source/markdown/podman-umount.1.md) | Unmount a working container's root filesystem | diff --git a/completions/bash/podman b/completions/bash/podman index 6d145030f..5634a5d8c 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1142,6 +1142,21 @@ _podman_container() { esac } +_podman_system_reset() { + local options_with_args=" + " + local boolean_options=" + -h + --help + --force + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac +} + _podman_system_df() { local options_with_args=" --format @@ -1193,6 +1208,7 @@ _podman_system() { df info prune + reset " __podman_subcommands "$subcommands" && return diff --git a/docs/source/markdown/podman-system-reset.1.md b/docs/source/markdown/podman-system-reset.1.md new file mode 100644 index 000000000..432f275f4 --- /dev/null +++ b/docs/source/markdown/podman-system-reset.1.md @@ -0,0 +1,25 @@ +% podman-system-reset(1) + +## NAME +podman\-system\-reset - Reset storage back to initial state + +## SYNOPSIS +**podman system reset** + +## DESCRIPTION +**podman system reset** removes all pods, containers, images and volumes. + +## OPTIONS +**--force**, **-f** + +Do not prompt for confirmation + +**--help**, **-h** + +Print usage statement + +## SEE ALSO +`podman(1)`, `podman-system(1)` + +## HISTORY +November 2019, Originally compiled by Dan Walsh (dwalsh at redhat dot com) diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md index bbd541066..1af97290d 100644 --- a/docs/source/markdown/podman-system.1.md +++ b/docs/source/markdown/podman-system.1.md @@ -15,9 +15,10 @@ The system command allows you to manage the podman systems | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | | df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. | | info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | -| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data | -| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks. | | migrate | [podman-system-migrate(1)](podman-system-migrate.1.md)| Migrate existing containers to a new podman version. | +| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. | +| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks. | +| reset | [podman-system-reset(1)](podman-system-reset.1.md) | Reset storage back to initial state. | ## SEE ALSO podman(1) diff --git a/libpod/reset.go b/libpod/reset.go new file mode 100644 index 000000000..a35b476a4 --- /dev/null +++ b/libpod/reset.go @@ -0,0 +1,107 @@ +package libpod + +import ( + "context" + "os" + "path/filepath" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// Reset removes all storage +func (r *Runtime) Reset(ctx context.Context) error { + + pods, err := r.GetAllPods() + if err != nil { + return err + } + for _, p := range pods { + if err := r.RemovePod(ctx, p, true, true); err != nil { + if errors.Cause(err) == define.ErrNoSuchPod { + continue + } + logrus.Errorf("Error removing Pod %s: %v", p.ID(), err) + } + } + + ctrs, err := r.GetAllContainers() + if err != nil { + return err + } + + for _, c := range ctrs { + if err := r.RemoveContainer(ctx, c, true, true); err != nil { + if err := r.RemoveStorageContainer(c.ID(), true); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + continue + } + logrus.Errorf("Error removing container %s: %v", c.ID(), err) + } + } + } + + if err := stopPauseProcess(); err != nil { + logrus.Errorf("Error stopping pause process: %v", err) + } + + ir := r.ImageRuntime() + images, err := ir.GetImages() + if err != nil { + return err + } + + for _, i := range images { + if err := i.Remove(ctx, true); err != nil { + if errors.Cause(err) == define.ErrNoSuchImage { + continue + } + logrus.Errorf("Error removing image %s: %v", i.ID(), err) + } + } + volumes, err := r.state.AllVolumes() + if err != nil { + return err + } + for _, v := range volumes { + if err := r.RemoveVolume(ctx, v, true); err != nil { + if errors.Cause(err) == define.ErrNoSuchVolume { + continue + } + logrus.Errorf("Error removing volume %s: %v", v.config.Name, err) + } + } + + _, prevError := r.store.Shutdown(true) + if err := os.RemoveAll(r.store.GraphRoot()); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + if err := os.RemoveAll(r.store.RunRoot()); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + if err := os.RemoveAll(r.config.TmpDir); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + if rootless.IsRootless() { + configPath := filepath.Join(os.Getenv("HOME"), ".config/containers") + if err := os.RemoveAll(configPath); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + } + + return prevError +} diff --git a/libpod/runtime_migrate_unsupported.go b/libpod/runtime_migrate_unsupported.go index 1a9e46fdc..e362cca63 100644 --- a/libpod/runtime_migrate_unsupported.go +++ b/libpod/runtime_migrate_unsupported.go @@ -9,3 +9,7 @@ import ( func (r *Runtime) migrate(ctx context.Context) error { return nil } + +func stopPauseProcess() error { + return nil +} diff --git a/pkg/adapter/reset.go b/pkg/adapter/reset.go new file mode 100644 index 000000000..0decc3d15 --- /dev/null +++ b/pkg/adapter/reset.go @@ -0,0 +1,13 @@ +// +build !remoteclient + +package adapter + +import ( + "context" +) + +// Reset the container storage back to initial states. +// Removes all Pods, Containers, Images and Volumes. +func (r *LocalRuntime) Reset() error { + return r.Runtime.Reset(context.TODO()) +} diff --git a/pkg/adapter/reset_remote.go b/pkg/adapter/reset_remote.go new file mode 100644 index 000000000..663fab639 --- /dev/null +++ b/pkg/adapter/reset_remote.go @@ -0,0 +1,12 @@ +// +build remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// Info returns information for the host system and its components +func (r RemoteRuntime) Reset() error { + return iopodman.Reset().Call(r.Conn) +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index f6057f5fc..b81ff11ba 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -3,12 +3,15 @@ package varlinkapi import ( + "context" "fmt" - "github.com/containers/libpod/libpod/define" + "os" goruntime "runtime" "time" "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/define" + "github.com/sirupsen/logrus" ) // GetVersion ... @@ -105,3 +108,20 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Insecure_registries = insecureRegistries return call.ReplyGetInfo(podmanInfo) } + +// GetVersion ... +func (i *LibpodAPI) Reset(call iopodman.VarlinkCall) error { + if err := i.Runtime.Reset(context.TODO()); err != nil { + logrus.Errorf("Reset Failed: %v", err) + if err := call.ReplyErrorOccurred(err.Error()); err != nil { + logrus.Errorf("Failed to send ReplyErrorOccurred: %v", err) + } + os.Exit(define.ExecErrorCodeGeneric) + } + if err := call.ReplyReset(); err != nil { + logrus.Errorf("Failed to send ReplyReset: %v", err) + os.Exit(define.ExecErrorCodeGeneric) + } + os.Exit(0) + return nil +} diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 5239f4d8e..43f08bf03 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -135,3 +135,7 @@ func removeCache() { func (p *PodmanTestIntegration) SeedImages() error { return nil } + +// We don't support running Varlink when local +func (p *PodmanTestIntegration) StartVarlink() { +} diff --git a/test/e2e/system_reset_test.go b/test/e2e/system_reset_test.go new file mode 100644 index 000000000..e5ce69739 --- /dev/null +++ b/test/e2e/system_reset_test.go @@ -0,0 +1,83 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("podman system reset", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman system reset", func() { + // system reset will not remove additional store images, so need to grab length + + session := podmanTest.Podman([]string{"rmi", "--force", "--all"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"images", "-n"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + l := len(session.OutputToStringArray()) + + session = podmanTest.Podman([]string{"pull", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "data"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "data:/data", ALPINE, "echo", "hello"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"system", "reset", "-f"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // If remote then the varlink service should have exited + // On local tests this is a noop + podmanTest.StartVarlink() + + session = podmanTest.Podman([]string{"images", "-n"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(l)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(0)) + + session = podmanTest.Podman([]string{"container", "ls", "-q"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(0)) + }) +}) -- cgit v1.2.3-54-g00ecf