From 37f3b191d5318b7d25893eabf4e57b568c326773 Mon Sep 17 00:00:00 2001 From: Sujil02 Date: Thu, 2 Apr 2020 22:56:59 -0400 Subject: [PATCH] Add pod prune for api v2. Add the ability to prune pods for api v2, Includes the addition of force flag, for client side prompt. Update test suite to support this use case. Signed-off-by: Sujil02 --- cmd/podman/pods/prune.go | 75 +++++++++++++++++++++++++ libpod/runtime_pod.go | 3 +- pkg/api/handlers/libpod/pods.go | 11 +++- pkg/api/handlers/libpod/swagger.go | 7 +++ pkg/api/server/register_pods.go | 6 +- pkg/bindings/pods/pods.go | 12 ++-- pkg/bindings/test/pods_test.go | 14 +++-- pkg/domain/entities/engine_container.go | 1 + pkg/domain/entities/pods.go | 9 +++ pkg/domain/entities/types.go | 2 - pkg/domain/infra/abi/pods.go | 17 ++++++ pkg/domain/infra/tunnel/pods.go | 4 ++ test/e2e/pod_prune_test.go | 23 +------- 13 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 cmd/podman/pods/prune.go diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go new file mode 100644 index 00000000000..091e3375b23 --- /dev/null +++ b/cmd/podman/pods/prune.go @@ -0,0 +1,75 @@ +package pods + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneOptions = entities.PodPruneOptions{} +) + +var ( + pruneDescription = fmt.Sprintf(`podman pod prune Removes all exited pods`) + + pruneCommand = &cobra.Command{ + Use: "prune [flags]", + Short: "Remove all stopped pods and their containers", + Long: pruneDescription, + RunE: prune, + Example: `podman pod prune`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCommand, + Parent: podCmd, + }) + flags := pruneCommand.Flags() + flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation. The default is false") +} + +func prune(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + if len(args) > 0 { + return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + if !pruneOptions.Force { + reader := bufio.NewReader(os.Stdin) + fmt.Println("WARNING! This will remove all stopped/exited pods..") + fmt.Print("Are you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + responses, err := registry.ContainerEngine().PodPrune(context.Background(), pruneOptions) + + if err != nil { + return err + } + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index be566e21108..5b81e166a74 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -176,8 +176,7 @@ func (r *Runtime) GetRunningPods() ([]*Pod, error) { } // PrunePods removes unused pods and their containers from local storage. -// If force is given, then running pods are also included in the pruning. -func (r *Runtime) PrunePods() (map[string]error, error) { +func (r *Runtime) PrunePods(ctx context.Context) (map[string]error, error) { response := make(map[string]error) states := []string{define.PodStateStopped, define.PodStateExited} filterFunc := func(p *Pod) bool { diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 92556bb617c..618d48ac0e6 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -232,13 +232,20 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { func PodPrune(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + reports []*entities.PodPruneReport ) - pruned, err := runtime.PrunePods() + responses, err := runtime.PrunePods(r.Context()) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, pruned) + for k, v := range responses { + reports = append(reports, &entities.PodPruneReport{ + Err: v, + Id: k, + }) + } + utils.WriteResponse(w, http.StatusOK, reports) } func PodPause(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index ed19462c62f..46426eb6bfd 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -70,6 +70,13 @@ type swagStartPodResponse struct { Body entities.PodStartReport } +// Prune pod +// swagger:response PodPruneReport +type swagPrunePodResponse struct { + // in:body + Body entities.PodPruneReport +} + // Rm pod // swagger:response PodRmReport type swagRmPodResponse struct { diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index a49bf1f639a..63060af41b2 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -53,11 +53,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // description: tbd - // schema: - // type: object - // additionalProperties: - // type: string + // $ref: '#/responses/PodPruneReport' // 400: // $ref: "#/responses/BadParamError" // 409: diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 83847614a6a..3c60fa2a031 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -98,17 +98,19 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro return &report, response.Process(&report) } -// Prune removes all non-running pods in local storage. -func Prune(ctx context.Context) error { +// Prune by default removes all non-running pods in local storage. +// And with force set true removes all pods. +func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) { + var reports []*entities.PodPruneReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil) if err != nil { - return err + return nil, err } - return response.Process(nil) + return reports, response.Process(&reports) } // List returns all pods in local storage. The optional filters parameter can diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 579161b266a..4d682a52251 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -262,7 +262,7 @@ var _ = Describe("Podman pods", func() { var newpod2 string = "newpod2" bt.Podcreate(&newpod2) // No pods pruned since no pod in exited state - err = pods.Prune(bt.conn) + pruneResponse, err := pods.Prune(bt.conn) Expect(err).To(BeNil()) podSummary, err := pods.List(bt.conn, nil) Expect(err).To(BeNil()) @@ -279,13 +279,19 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) // FIXME sujil please fix this //Expect(response.State.Status).To(Equal(define.PodStateExited)) - err = pods.Prune(bt.conn) + pruneResponse, err = pods.Prune(bt.conn) Expect(err).To(BeNil()) + // Validate status and record pod id of pod to be pruned + //Expect(response.State.Status).To(Equal(define.PodStateExited)) + //podID := response.Config.ID + // Check if right pod was pruned + Expect(len(pruneResponse)).To(Equal(1)) + // One pod is pruned hence only one pod should be active. podSummary, err = pods.List(bt.conn, nil) Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(1)) - // Test prune all pods in exited state. + // Test prune multiple pods. bt.Podcreate(&newpod) _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) @@ -311,7 +317,7 @@ var _ = Describe("Podman pods", func() { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) } - err = pods.Prune(bt.conn) + _, err = pods.Prune(bt.conn) Expect(err).To(BeNil()) podSummary, err = pods.List(bt.conn, nil) Expect(err).To(BeNil()) diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 02938413a64..b730f874366 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -49,6 +49,7 @@ type ContainerEngine interface { PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error) PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error) PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error) + PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error) PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error) PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index b280203ded2..04673ef18b0 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -147,6 +147,15 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { s.CgroupParent = p.CGroupParent } +type PodPruneOptions struct { + Force bool `json:"force" schema:"force"` +} + +type PodPruneReport struct { + Err error + Id string +} + type PodTopOptions struct { // CLI flags. ListDescriptors bool diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index e4e1c3ad2ca..b89aa869a0d 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -25,9 +25,7 @@ type Report struct { } type PodDeleteReport struct{ Report } -type PodPruneOptions struct{} -type PodPruneReport struct{ Report } type VolumeDeleteOptions struct{} type VolumeDeleteReport struct{ Report } diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 59bf0f63683..6b6e13e2405 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -243,6 +243,23 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio return reports, nil } +func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) { + var ( + reports []*entities.PodPruneReport + ) + response, err := ic.Libpod.PrunePods(ctx) + if err != nil { + return nil, err + } + for k, v := range response { + reports = append(reports, &entities.PodPruneReport{ + Err: v, + Id: k, + }) + } + return reports, nil +} + func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { podSpec := specgen.NewPodSpecGenerator() opts.ToPodSpecGen(podSpec) diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index dad77284fa0..e7641c07764 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -173,6 +173,10 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio return reports, nil } +func (ic *ContainerEngine) PodPrune(ctx context.Context, opts entities.PodPruneOptions) ([]*entities.PodPruneReport, error) { + return pods.Prune(ic.ClientCxt) +} + func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { podSpec := specgen.NewPodSpecGenerator() opts.ToPodSpecGen(podSpec) diff --git a/test/e2e/pod_prune_test.go b/test/e2e/pod_prune_test.go index 389d3cb27ec..d98383331aa 100644 --- a/test/e2e/pod_prune_test.go +++ b/test/e2e/pod_prune_test.go @@ -36,7 +36,7 @@ var _ = Describe("Podman pod prune", func() { _, ec, _ := podmanTest.CreatePod("") Expect(ec).To(Equal(0)) - result := podmanTest.Podman([]string{"pod", "prune"}) + result := podmanTest.Podman([]string{"pod", "prune", "--force"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) }) @@ -49,7 +49,7 @@ var _ = Describe("Podman pod prune", func() { ec2.WaitWithDefaultTimeout() Expect(ec2.ExitCode()).To(Equal(0)) - result := podmanTest.Podman([]string{"pod", "prune"}) + result := podmanTest.Podman([]string{"pod", "prune", "-f"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To((Equal(0))) @@ -65,7 +65,7 @@ var _ = Describe("Podman pod prune", func() { _, ec2, _ := podmanTest.RunLsContainerInPod("", podid) Expect(ec2).To(Equal(0)) - result := podmanTest.Podman([]string{"pod", "prune"}) + result := podmanTest.Podman([]string{"pod", "prune", "-f"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -73,21 +73,4 @@ var _ = Describe("Podman pod prune", func() { result.WaitWithDefaultTimeout() Expect(len(result.OutputToStringArray())).To(Equal(0)) }) - - It("podman pod prune -f does remove a running container", func() { - _, ec, podid := podmanTest.CreatePod("") - Expect(ec).To(Equal(0)) - - session := podmanTest.RunTopContainerInPod("", podid) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - - result := podmanTest.Podman([]string{"pod", "prune", "-f"}) - result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) - - result = podmanTest.Podman([]string{"ps", "-q"}) - result.WaitWithDefaultTimeout() - Expect(result.OutputToString()).To(BeEmpty()) - }) })