mirror of
https://github.com/containers/podman.git
synced 2025-06-22 01:48:54 +08:00
Merge pull request #5775 from sujil02/v2-container-prune
Ability to prune container in api V2
This commit is contained in:
@ -19,12 +19,12 @@ var (
|
|||||||
pruneContainersDescription = `
|
pruneContainersDescription = `
|
||||||
podman container prune
|
podman container prune
|
||||||
|
|
||||||
Removes all exited containers
|
Removes all stopped | exited containers
|
||||||
`
|
`
|
||||||
_pruneContainersCommand = &cobra.Command{
|
_pruneContainersCommand = &cobra.Command{
|
||||||
Use: "prune",
|
Use: "prune",
|
||||||
Args: noSubArgs,
|
Args: noSubArgs,
|
||||||
Short: "Remove all stopped containers",
|
Short: "Remove all stopped | exited containers",
|
||||||
Long: pruneContainersDescription,
|
Long: pruneContainersDescription,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
pruneContainersCommand.InputArgs = args
|
pruneContainersCommand.InputArgs = args
|
||||||
|
86
cmd/podmanV2/containers/prune.go
Normal file
86
cmd/podmanV2/containers/prune.go
Normal file
@ -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=<key>=<value>')")
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
@ -887,8 +887,9 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = r.RemoveContainer(context.Background(), ctr, false, false)
|
err = r.RemoveContainer(context.Background(), ctr, false, false)
|
||||||
pruneErrors[ctr.ID()] = err
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
pruneErrors[ctr.ID()] = err
|
||||||
|
} else {
|
||||||
prunedContainers[ctr.ID()] = size
|
prunedContainers[ctr.ID()] = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"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/api/handlers/utils"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -40,14 +40,11 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Libpod response differs
|
// Libpod response differs
|
||||||
if utils.IsLibpodRequest(r) {
|
if utils.IsLibpodRequest(r) {
|
||||||
var response []handlers.LibpodContainersPruneReport
|
report := &entities.ContainerPruneReport{
|
||||||
for ctrID, size := range prunedContainers {
|
Err: pruneErrors,
|
||||||
response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size})
|
ID: prunedContainers,
|
||||||
}
|
}
|
||||||
for ctrID, err := range pruneErrors {
|
utils.WriteResponse(w, http.StatusOK, report)
|
||||||
response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()})
|
|
||||||
}
|
|
||||||
utils.WriteResponse(w, http.StatusOK, response)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for ctrID, size := range prunedContainers {
|
for ctrID, size := range prunedContainers {
|
||||||
|
@ -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
|
// 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
|
// errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse
|
||||||
// structure.
|
// structure.
|
||||||
func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
|
func Prune(ctx context.Context, filters map[string][]string) (*entities.ContainerPruneReport, error) {
|
||||||
var (
|
var reports *entities.ContainerPruneReport
|
||||||
pruneResponse []string
|
|
||||||
)
|
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
|
||||||
if err != nil {
|
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
|
// Remove removes a container from local storage. The force bool designates
|
||||||
|
@ -531,4 +531,69 @@ var _ = Describe("Podman containers ", func() {
|
|||||||
Expect(err).ToNot(BeNil())
|
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))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ package entities
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -260,7 +261,7 @@ type ContainerRunOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainerRunReport describes the results of running
|
// ContainerRunReport describes the results of running
|
||||||
//a container
|
// a container
|
||||||
type ContainerRunReport struct {
|
type ContainerRunReport struct {
|
||||||
ExitCode int
|
ExitCode int
|
||||||
Id string
|
Id string
|
||||||
@ -327,3 +328,16 @@ type ContainerUnmountReport struct {
|
|||||||
Err error
|
Err error
|
||||||
Id string
|
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
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ type ContainerEngine interface {
|
|||||||
ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error
|
ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error
|
||||||
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
||||||
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, 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)
|
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
||||||
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
||||||
ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
|
ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/containers/libpod/libpod/events"
|
"github.com/containers/libpod/libpod/events"
|
||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
"github.com/containers/libpod/libpod/logs"
|
"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/checkpoint"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
|
"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
|
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) {
|
func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, options entities.KillOptions) ([]*entities.KillReport, error) {
|
||||||
var (
|
var (
|
||||||
reports []*entities.KillReport
|
reports []*entities.KillReport
|
||||||
|
@ -146,6 +146,10 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
|
|||||||
return reports, nil
|
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) {
|
func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) {
|
||||||
var (
|
var (
|
||||||
reports []*entities.ContainerInspectReport
|
reports []*entities.ContainerInspectReport
|
||||||
|
Reference in New Issue
Block a user