From ec4060aef6c77c049fc2c3b6438ebb5590f6ab69 Mon Sep 17 00:00:00 2001 From: Sujil02 Date: Wed, 8 Apr 2020 04:49:32 -0400 Subject: Ability to prune container in api V2 Adds ability to prune containers for v2. Adds client side prompt with force flag and filters options to prune. Signed-off-by: Sujil02 --- cmd/podman/containers_prune.go | 4 +- cmd/podmanV2/containers/prune.go | 86 +++++++++++++++++++++++++++++ libpod/runtime_ctr.go | 3 +- pkg/api/handlers/compat/containers_prune.go | 13 ++--- pkg/bindings/containers/containers.go | 10 ++-- pkg/bindings/test/containers_test.go | 65 ++++++++++++++++++++++ pkg/domain/entities/containers.go | 16 +++++- pkg/domain/entities/engine_container.go | 1 + pkg/domain/infra/abi/containers.go | 17 ++++++ pkg/domain/infra/tunnel/containers.go | 4 ++ 10 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 cmd/podmanV2/containers/prune.go diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index cd9817e7e..3953a489d 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -19,12 +19,12 @@ var ( pruneContainersDescription = ` podman container prune - Removes all exited containers + Removes all stopped | exited containers ` _pruneContainersCommand = &cobra.Command{ Use: "prune", Args: noSubArgs, - Short: "Remove all stopped containers", + Short: "Remove all stopped | exited containers", Long: pruneContainersDescription, RunE: func(cmd *cobra.Command, args []string) error { pruneContainersCommand.InputArgs = args diff --git a/cmd/podmanV2/containers/prune.go b/cmd/podmanV2/containers/prune.go new file mode 100644 index 000000000..2d3af5d1d --- /dev/null +++ b/cmd/podmanV2/containers/prune.go @@ -0,0 +1,86 @@ +package containers + +import ( + "bufio" + "context" + "fmt" + "net/url" + "os" + "strings" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneDescription = fmt.Sprintf(`podman container prune + + Removes all stopped | exited containers`) + pruneCommand = &cobra.Command{ + Use: "prune [flags]", + Short: "Remove all stopped | exited containers", + Long: pruneDescription, + RunE: prune, + Example: `podman container prune`, + } + force bool + filter = []string{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCommand, + Parent: containerCmd, + }) + flags := pruneCommand.Flags() + flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") + flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label==')") +} + +func prune(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + pruneOptions = entities.ContainerPruneOptions{} + ) + if len(args) > 0 { + return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + if !force { + reader := bufio.NewReader(os.Stdin) + fmt.Println("WARNING! This will remove all stopped containers.") + 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 + } + } + + // TODO Remove once filter refactor is finished and url.Values done. + for _, f := range filter { + t := strings.SplitN(f, "=", 2) + pruneOptions.Filters = make(url.Values) + if len(t) < 2 { + return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + pruneOptions.Filters.Add(t[0], t[1]) + } + responses, err := registry.ContainerEngine().ContainerPrune(context.Background(), pruneOptions) + + if err != nil { + return err + } + for k := range responses.ID { + fmt.Println(k) + } + for _, v := range responses.Err { + errs = append(errs, v) + } + return errs.PrintErrors() +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 207ac6477..9d3e69d56 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -887,8 +887,9 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int continue } err = r.RemoveContainer(context.Background(), ctr, false, false) - pruneErrors[ctr.ID()] = err if err != nil { + pruneErrors[ctr.ID()] = err + } else { prunedContainers[ctr.ID()] = size } } diff --git a/pkg/api/handlers/compat/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go index a56c3903d..bf3aecd65 100644 --- a/pkg/api/handlers/compat/containers_prune.go +++ b/pkg/api/handlers/compat/containers_prune.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/docker/docker/api/types" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -40,14 +40,11 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) { // Libpod response differs if utils.IsLibpodRequest(r) { - var response []handlers.LibpodContainersPruneReport - for ctrID, size := range prunedContainers { - response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size}) + report := &entities.ContainerPruneReport{ + Err: pruneErrors, + ID: prunedContainers, } - for ctrID, err := range pruneErrors { - response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()}) - } - utils.WriteResponse(w, http.StatusOK, response) + utils.WriteResponse(w, http.StatusOK, report) return } for ctrID, size := range prunedContainers { diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 963f0ec57..e74a256c7 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -60,10 +60,8 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int // used for more granular selection of containers. The main error returned indicates if there were runtime // errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse // structure. -func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { - var ( - pruneResponse []string - ) +func Prune(ctx context.Context, filters map[string][]string) (*entities.ContainerPruneReport, error) { + var reports *entities.ContainerPruneReport conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -78,9 +76,9 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params) if err != nil { - return pruneResponse, err + return nil, err } - return pruneResponse, response.Process(pruneResponse) + return reports, response.Process(&reports) } // Remove removes a container from local storage. The force bool designates diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 0b1b9ecdd..e288dc368 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -531,4 +531,69 @@ var _ = Describe("Podman containers ", func() { Expect(err).ToNot(BeNil()) }) + It("podman prune stoped containers", func() { + // Start and stop a container to enter in exited state. + var name = "top" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // Prune container should return no errors and one pruned container ID. + pruneResponse, err := containers.Prune(bt.conn, nil) + Expect(err).To(BeNil()) + Expect(len(pruneResponse.Err)).To(Equal(0)) + Expect(len(pruneResponse.ID)).To(Equal(1)) + }) + + It("podman prune stoped containers with filters", func() { + // Start and stop a container to enter in exited state. + var name = "top" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // Invalid filter keys should return error. + filtersIncorrect := map[string][]string{ + "status": {"dummy"}, + } + pruneResponse, err := containers.Prune(bt.conn, filtersIncorrect) + Expect(err).ToNot(BeNil()) + + // Mismatched filter params no container should be pruned. + filtersIncorrect = map[string][]string{ + "name": {"r"}, + } + pruneResponse, err = containers.Prune(bt.conn, filtersIncorrect) + Expect(err).To(BeNil()) + Expect(len(pruneResponse.Err)).To(Equal(0)) + Expect(len(pruneResponse.ID)).To(Equal(0)) + + // Valid filter params container should be pruned now. + filters := map[string][]string{ + "name": {"top"}, + } + pruneResponse, err = containers.Prune(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(pruneResponse.Err)).To(Equal(0)) + Expect(len(pruneResponse.ID)).To(Equal(1)) + }) + + It("podman prune running containers", func() { + // Start the container. + var name = "top" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + // Check if the container is running. + data, err := containers.Inspect(bt.conn, name, nil) + Expect(err).To(BeNil()) + Expect(data.State.Status).To(Equal("running")) + + // Prune. Should return no error no prune response ID. + pruneResponse, err := containers.Prune(bt.conn, nil) + Expect(err).To(BeNil()) + Expect(len(pruneResponse.ID)).To(Equal(0)) + }) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index f21af9ce4..52327a905 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -2,6 +2,7 @@ package entities import ( "io" + "net/url" "os" "time" @@ -260,7 +261,7 @@ type ContainerRunOptions struct { } // ContainerRunReport describes the results of running -//a container +// a container type ContainerRunReport struct { ExitCode int Id string @@ -327,3 +328,16 @@ type ContainerUnmountReport struct { Err error Id string } + +// ContainerPruneOptions describes the options needed +// to prune a container from the CLI +type ContainerPruneOptions struct { + Filters url.Values `json:"filters" schema:"filters"` +} + +// ContainerPruneReport describes the results after pruning the +// stopped containers. +type ContainerPruneReport struct { + ID map[string]int64 + Err map[string]error +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 2001ab49c..24e7995e7 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -13,6 +13,7 @@ type ContainerEngine interface { ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error) + ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error) ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index f464df3ac..fc62a6c29 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -19,6 +19,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" + "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi/terminal" @@ -173,6 +174,22 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin return reports, nil } +func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities.ContainerPruneOptions) (*entities.ContainerPruneReport, error) { + filterFuncs, err := utils.GenerateFilterFuncsFromMap(ic.Libpod, options.Filters) + if err != nil { + return nil, err + } + prunedContainers, pruneErrors, err := ic.Libpod.PruneContainers(filterFuncs) + if err != nil { + return nil, err + } + report := entities.ContainerPruneReport{ + ID: prunedContainers, + Err: pruneErrors, + } + return &report, nil +} + func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, options entities.KillOptions) ([]*entities.KillReport, error) { var ( reports []*entities.KillReport diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 05b62efcf..679bb371b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -146,6 +146,10 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, return reports, nil } +func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities.ContainerPruneOptions) (*entities.ContainerPruneReport, error) { + return containers.Prune(ic.ClientCxt, options.Filters) +} + func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { var ( reports []*entities.ContainerInspectReport -- cgit v1.2.3-54-g00ecf