mirror of
https://github.com/containers/podman.git
synced 2025-06-10 01:31:58 +08:00
V2 podman image prune
* Fixed header for `podman image ls` * Implemented prune `all` flag, preserved filter method for backwards capability * Updated binding tests Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
@ -15,7 +15,7 @@ var (
|
|||||||
Args: listCmd.Args,
|
Args: listCmd.Args,
|
||||||
Short: listCmd.Short,
|
Short: listCmd.Short,
|
||||||
Long: listCmd.Long,
|
Long: listCmd.Long,
|
||||||
PreRunE: listCmd.PreRunE,
|
PreRunE: preRunE,
|
||||||
RunE: listCmd.RunE,
|
RunE: listCmd.RunE,
|
||||||
Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
|
Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ func imageListFormat(flags listFlagType) (string, string) {
|
|||||||
row += "\t{{.Digest}}"
|
row += "\t{{.Digest}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr += "\tID"
|
hdr += "\tIMAGE ID"
|
||||||
if flags.noTrunc {
|
if flags.noTrunc {
|
||||||
row += "\tsha256:{{.ID}}"
|
row += "\tsha256:{{.ID}}"
|
||||||
} else {
|
} else {
|
||||||
|
86
cmd/podmanV2/images/prune.go
Normal file
86
cmd/podmanV2/images/prune.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pruneDescription = `Removes all unnamed images from local storage.
|
||||||
|
|
||||||
|
If an image is not being used by a container, it will be removed from the system.`
|
||||||
|
pruneCmd = &cobra.Command{
|
||||||
|
Use: "prune",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Short: "Remove unused images",
|
||||||
|
Long: pruneDescription,
|
||||||
|
RunE: prune,
|
||||||
|
Example: `podman image prune`,
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneOpts = entities.ImagePruneOptions{}
|
||||||
|
force bool
|
||||||
|
filter = []string{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: pruneCmd,
|
||||||
|
Parent: imageCmd,
|
||||||
|
})
|
||||||
|
|
||||||
|
flags := pruneCmd.Flags()
|
||||||
|
flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones")
|
||||||
|
flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func prune(cmd *cobra.Command, args []string) error {
|
||||||
|
if !force {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Printf(`
|
||||||
|
WARNING! This will remove all dangling images.
|
||||||
|
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 rules :)
|
||||||
|
for _, f := range filter {
|
||||||
|
t := strings.SplitN(f, "=", 2)
|
||||||
|
pruneOpts.Filters.Add(t[0], t[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := registry.ImageEngine().Prune(registry.GetContext(), pruneOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range results.Report.Id {
|
||||||
|
fmt.Println(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range results.Report.Err {
|
||||||
|
fmt.Fprint(os.Stderr, e.Error()+"\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if results.Size > 0 {
|
||||||
|
fmt.Fprintf(os.Stdout, "Size: %d\n", results.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -64,6 +64,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
|
|
||||||
query := struct {
|
query := struct {
|
||||||
|
All bool
|
||||||
Filters map[string][]string `schema:"filters"`
|
Filters map[string][]string `schema:"filters"`
|
||||||
}{
|
}{
|
||||||
// This is where you can override the golang default value for one of fields
|
// This is where you can override the golang default value for one of fields
|
||||||
@ -80,7 +81,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
|
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters)
|
pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -119,12 +119,12 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func PruneImages(w http.ResponseWriter, r *http.Request) {
|
func PruneImages(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
all bool
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
query := struct {
|
query := struct {
|
||||||
|
All bool `schema:"all"`
|
||||||
Filters map[string][]string `schema:"filters"`
|
Filters map[string][]string `schema:"filters"`
|
||||||
}{
|
}{
|
||||||
// override any golang type defaults
|
// override any golang type defaults
|
||||||
@ -140,7 +140,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
if _, found := r.URL.Query()["filters"]; found {
|
if _, found := r.URL.Query()["filters"]; found {
|
||||||
dangling := query.Filters["all"]
|
dangling := query.Filters["all"]
|
||||||
if len(dangling) > 0 {
|
if len(dangling) > 0 {
|
||||||
all, err = strconv.ParseBool(query.Filters["all"][0])
|
query.All, err = strconv.ParseBool(query.Filters["all"][0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
@ -152,7 +152,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
|
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters)
|
|
||||||
|
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
|
@ -154,7 +154,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c
|
|||||||
|
|
||||||
// Prune removes unused images from local storage. The optional filters can be used to further
|
// Prune removes unused images from local storage. The optional filters can be used to further
|
||||||
// define which images should be pruned.
|
// define which images should be pruned.
|
||||||
func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
|
func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]string, error) {
|
||||||
var (
|
var (
|
||||||
deleted []string
|
deleted []string
|
||||||
)
|
)
|
||||||
@ -163,6 +163,9 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
|
if all != nil {
|
||||||
|
params.Set("all", strconv.FormatBool(*all))
|
||||||
|
}
|
||||||
if filters != nil {
|
if filters != nil {
|
||||||
stringFilter, err := bindings.FiltersToString(filters)
|
stringFilter, err := bindings.FiltersToString(filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -174,7 +177,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return deleted, err
|
return deleted, err
|
||||||
}
|
}
|
||||||
return deleted, response.Process(nil)
|
return deleted, response.Process(&deleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
|
// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
|
||||||
|
@ -16,22 +16,22 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("Podman images", func() {
|
var _ = Describe("Podman images", func() {
|
||||||
var (
|
var (
|
||||||
//tempdir string
|
// tempdir string
|
||||||
//err error
|
// err error
|
||||||
//podmanTest *PodmanTestIntegration
|
// podmanTest *PodmanTestIntegration
|
||||||
bt *bindingTest
|
bt *bindingTest
|
||||||
s *gexec.Session
|
s *gexec.Session
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
//tempdir, err = CreateTempDirInTempDir()
|
// tempdir, err = CreateTempDirInTempDir()
|
||||||
//if err != nil {
|
// if err != nil {
|
||||||
// os.Exit(1)
|
// os.Exit(1)
|
||||||
//}
|
// }
|
||||||
//podmanTest = PodmanTestCreate(tempdir)
|
// podmanTest = PodmanTestCreate(tempdir)
|
||||||
//podmanTest.Setup()
|
// podmanTest.Setup()
|
||||||
//podmanTest.SeedImages()
|
// podmanTest.SeedImages()
|
||||||
bt = newBindingTest()
|
bt = newBindingTest()
|
||||||
bt.RestoreImagesFromCache()
|
bt.RestoreImagesFromCache()
|
||||||
s = bt.startAPIService()
|
s = bt.startAPIService()
|
||||||
@ -41,12 +41,13 @@ var _ = Describe("Podman images", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
//podmanTest.Cleanup()
|
// podmanTest.Cleanup()
|
||||||
//f := CurrentGinkgoTestDescription()
|
// f := CurrentGinkgoTestDescription()
|
||||||
//processTestResult(f)
|
// processTestResult(f)
|
||||||
s.Kill()
|
s.Kill()
|
||||||
bt.cleanup()
|
bt.cleanup()
|
||||||
})
|
})
|
||||||
|
|
||||||
It("inspect image", func() {
|
It("inspect image", func() {
|
||||||
// Inspect invalid image be 404
|
// Inspect invalid image be 404
|
||||||
_, err = images.GetImage(bt.conn, "foobar5000", nil)
|
_, err = images.GetImage(bt.conn, "foobar5000", nil)
|
||||||
@ -71,7 +72,7 @@ var _ = Describe("Podman images", func() {
|
|||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
// TODO it looks like the images API alwaays returns size regardless
|
// TODO it looks like the images API alwaays returns size regardless
|
||||||
// of bool or not. What should we do ?
|
// of bool or not. What should we do ?
|
||||||
//Expect(data.Size).To(BeZero())
|
// Expect(data.Size).To(BeZero())
|
||||||
|
|
||||||
// Enabling the size parameter should result in size being populated
|
// Enabling the size parameter should result in size being populated
|
||||||
data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue)
|
data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue)
|
||||||
@ -142,7 +143,7 @@ var _ = Describe("Podman images", func() {
|
|||||||
err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName)
|
err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
//Validates if name updates when the image is retagged.
|
// Validates if name updates when the image is retagged.
|
||||||
_, err := images.GetImage(bt.conn, "alpine:demo", nil)
|
_, err := images.GetImage(bt.conn, "alpine:demo", nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
@ -165,7 +166,7 @@ var _ = Describe("Podman images", func() {
|
|||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(len(imageSummary)).To(Equal(3))
|
Expect(len(imageSummary)).To(Equal(3))
|
||||||
|
|
||||||
//Validate the image names.
|
// Validate the image names.
|
||||||
var names []string
|
var names []string
|
||||||
for _, i := range imageSummary {
|
for _, i := range imageSummary {
|
||||||
names = append(names, i.RepoTags...)
|
names = append(names, i.RepoTags...)
|
||||||
@ -289,6 +290,7 @@ var _ = Describe("Podman images", func() {
|
|||||||
Expect(data.Comment).To(Equal(testMessage))
|
Expect(data.Comment).To(Equal(testMessage))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("History Image", func() {
|
It("History Image", func() {
|
||||||
// a bogus name should return a 404
|
// a bogus name should return a 404
|
||||||
_, err := images.History(bt.conn, "foobar")
|
_, err := images.History(bt.conn, "foobar")
|
||||||
@ -343,4 +345,12 @@ var _ = Describe("Podman images", func() {
|
|||||||
Expect(len(imgs)).To(BeNumerically(">=", 1))
|
Expect(len(imgs)).To(BeNumerically(">=", 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Prune images", func() {
|
||||||
|
trueBoxed := true
|
||||||
|
results, err := images.Prune(bt.conn, &trueBoxed, nil)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(results)).To(BeNumerically(">", 0))
|
||||||
|
Expect(results).To(ContainElement("docker.io/library/alpine:latest"))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -119,7 +119,7 @@ type ImageInspectOptions struct {
|
|||||||
|
|
||||||
type ImageListOptions struct {
|
type ImageListOptions struct {
|
||||||
All bool `json:"all" schema:"all"`
|
All bool `json:"all" schema:"all"`
|
||||||
Filter []string `json:",omitempty"`
|
Filter []string `json:"Filter,omitempty"`
|
||||||
Filters url.Values `json:"filters" schema:"filters"`
|
Filters url.Values `json:"filters" schema:"filters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,8 +128,9 @@ type ImageListOptions struct {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
type ImagePruneOptions struct {
|
type ImagePruneOptions struct {
|
||||||
All bool
|
All bool `json:"all" schema:"all"`
|
||||||
Filter ImageFilter
|
Filter []string `json:"filter" schema:"filter"`
|
||||||
|
Filters url.Values `json:"filters" schema:"filters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImagePruneReport struct {
|
type ImagePruneReport struct {
|
||||||
|
@ -88,13 +88,18 @@ func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
|
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
|
||||||
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{})
|
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
report := entities.ImagePruneReport{}
|
report := entities.ImagePruneReport{
|
||||||
copy(report.Report.Id, results)
|
Report: entities.Report{
|
||||||
|
Id: results,
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
Size: 0,
|
||||||
|
}
|
||||||
return &report, nil
|
return &report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package tunnel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
images "github.com/containers/libpod/pkg/bindings/images"
|
images "github.com/containers/libpod/pkg/bindings/images"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
@ -72,12 +71,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
|
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
|
||||||
results, err := images.Prune(ir.ClientCxt, url.Values{})
|
results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
report := entities.ImagePruneReport{}
|
report := entities.ImagePruneReport{
|
||||||
copy(report.Report.Id, results)
|
Report: entities.Report{
|
||||||
|
Id: results,
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
Size: 0,
|
||||||
|
}
|
||||||
return &report, nil
|
return &report, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user