support pruning images

This commit is contained in:
Jesse Duffield 2019-05-19 01:44:52 +10:00
parent 4fe26f4f88
commit 15bd991560
7 changed files with 128 additions and 67 deletions

View file

@ -6,6 +6,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/i18n" "github.com/jesseduffield/lazydocker/pkg/i18n"
@ -84,9 +85,12 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
name = tags[0] name = tags[0]
} }
nameParts := strings.Split(name, ":")
ownImages[i] = &Image{ ownImages[i] = &Image{
ID: image.ID, ID: image.ID,
Name: name, Name: nameParts[0],
Tag: nameParts[1],
Image: image, Image: image,
Client: c.Client, Client: c.Client,
OSCommand: c.OSCommand, OSCommand: c.OSCommand,
@ -96,3 +100,9 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
return ownImages, nil return ownImages, nil
} }
// PruneImages prunes images
func (c *DockerCommand) PruneImages() error {
_, err := c.Client.ImagesPrune(context.Background(), filters.Args{})
return err
}

View file

@ -1,6 +1,8 @@
package commands package commands
import ( import (
"context"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/fatih/color" "github.com/fatih/color"
@ -11,6 +13,7 @@ import (
// Image : A docker Image // Image : A docker Image
type Image struct { type Image struct {
Name string Name string
Tag string
ID string ID string
Image types.ImageSummary Image types.ImageSummary
Client *client.Client Client *client.Client
@ -20,5 +23,14 @@ type Image struct {
// GetDisplayStrings returns the display string of Image // GetDisplayStrings returns the display string of Image
func (i *Image) GetDisplayStrings(isFocused bool) []string { func (i *Image) GetDisplayStrings(isFocused bool) []string {
return []string{utils.ColoredString(i.Name, color.FgWhite)} return []string{utils.ColoredString(i.Name, color.FgWhite), utils.ColoredString(i.Tag, color.FgWhite)}
}
// Remove removes the image
func (i *Image) Remove(options types.ImageRemoveOptions) error {
if _, err := i.Client.ImageRemove(context.Background(), i.ID, options); err != nil {
return err
}
return nil
} }

View file

@ -293,7 +293,7 @@ func (gui *Gui) handleContainersNextContext(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
type removeOption struct { type removeContainerOption struct {
description string description string
command string command string
configOptions types.ContainerRemoveOptions configOptions types.ContainerRemoveOptions
@ -301,7 +301,7 @@ type removeOption struct {
} }
// GetDisplayStrings is a function. // GetDisplayStrings is a function.
func (r *removeOption) GetDisplayStrings(isFocused bool) []string { func (r *removeContainerOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)} return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
} }
@ -311,7 +311,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
options := []*removeOption{ options := []*removeContainerOption{
{ {
description: gui.Tr.SLocalize("remove"), description: gui.Tr.SLocalize("remove"),
command: "docker rm " + container.ID[1:10], command: "docker rm " + container.ID[1:10],
@ -339,7 +339,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
var originalErr commands.ComplexError var originalErr commands.ComplexError
if xerrors.As(cerr, &originalErr) { if xerrors.As(cerr, &originalErr) {
if originalErr.Code == commands.MustStopContainer { if originalErr.Code == commands.MustStopContainer {
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("mustForceToRemove"), func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("mustForceToRemoveContainer"), func(g *gocui.Gui, v *gocui.View) error {
configOptions.Force = true configOptions.Force = true
if err := container.Remove(configOptions); err != nil { if err := container.Remove(configOptions); err != nil {
return err return err
@ -348,7 +348,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
}, nil) }, nil)
} }
} else { } else {
return gui.createErrorPanel(gui.g, err.Error()) return gui.createErrorPanel(gui.g, cerr.Error())
} }
} }

View file

@ -291,7 +291,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
"images": defaultHeight, "images": defaultHeight,
"options": defaultHeight, "options": defaultHeight,
} }
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1 vHeights[currentCyclebleView] = height - defaultHeight*2 - 1
} }
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1) optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/docker/docker/api/types"
"github.com/fatih/color"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
@ -206,67 +208,66 @@ func (gui *Gui) handleImagesNextContext(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
// type removeOption struct { type removeImageOption struct {
// description string description string
// command string command string
// configOptions types.ImageRemoveOptions configOptions types.ImageRemoveOptions
// runCommand bool runCommand bool
// } }
// // GetDisplayStrings is a function. // GetDisplayStrings is a function.
// func (r *removeOption) GetDisplayStrings(isFocused bool) []string { func (r *removeImageOption) GetDisplayStrings(isFocused bool) []string {
// return []string{r.description, color.New(color.FgRed).Sprint(r.command)} return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
// } }
// func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
// Image, err := gui.getSelectedImage(g) Image, err := gui.getSelectedImage(g)
// if err != nil { if err != nil {
// return nil return nil
// } }
// options := []*removeOption{ options := []*removeImageOption{
// { {
// description: gui.Tr.SLocalize("remove"), description: gui.Tr.SLocalize("remove"),
// command: "docker rm " + Image.ID[1:10], command: "docker image rm " + Image.ID[1:20],
// configOptions: types.ImageRemoveOptions{}, configOptions: types.ImageRemoveOptions{PruneChildren: true},
// runCommand: true, runCommand: true,
// }, },
// { {
// description: gui.Tr.SLocalize("removeWithVolumes"), description: gui.Tr.SLocalize("removeWithoutPrune"),
// command: "docker rm --volumes " + Image.ID[1:10], command: "docker image rm --no-prune " + Image.ID[1:20],
// configOptions: types.ImageRemoveOptions{RemoveVolumes: true}, configOptions: types.ImageRemoveOptions{PruneChildren: false},
// runCommand: true, runCommand: true,
// }, },
// { {
// description: gui.Tr.SLocalize("cancel"), description: gui.Tr.SLocalize("cancel"),
// runCommand: false, runCommand: false,
// }, },
// } }
// handleMenuPress := func(index int) error { handleMenuPress := func(index int) error {
// if !options[index].runCommand { if !options[index].runCommand {
// return nil return nil
// } }
// configOptions := options[index].configOptions configOptions := options[index].configOptions
// if cerr := Image.Remove(configOptions); cerr != nil { if cerr := Image.Remove(configOptions); cerr != nil {
// var originalErr commands.ComplexError return gui.createErrorPanel(gui.g, cerr.Error())
// if xerrors.As(cerr, &originalErr) { }
// if originalErr.Code == commands.MustStopImage {
// return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("mustForceToRemove"), func(g *gocui.Gui, v *gocui.View) error {
// configOptions.Force = true
// if err := Image.Remove(configOptions); err != nil {
// return err
// }
// return gui.refreshImages()
// }, nil)
// }
// } else {
// return gui.createErrorPanel(gui.g, err.Error())
// }
// }
// return gui.refreshImages() return gui.refreshImages()
// } }
// return gui.createMenu("", options, len(options), handleMenuPress) return gui.createMenu("", options, len(options), handleMenuPress)
// } }
func (gui *Gui) handlePruneImages(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("confirmPruneImages"), func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.SLocalize("PruningStatus"), func() error {
err := gui.DockerCommand.PruneImages()
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.refreshImages()
})
}, nil)
}

View file

@ -192,6 +192,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleImagesNextContext, Handler: gui.handleImagesNextContext,
Description: gui.Tr.SLocalize("nextContext"), Description: gui.Tr.SLocalize("nextContext"),
}, },
{
ViewName: "images",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleImagesRemoveMenu,
Description: gui.Tr.SLocalize("removeImage"),
},
{
ViewName: "images",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handlePruneImages,
Description: gui.Tr.SLocalize("pruneImages"),
},
} }
// TODO: add more views here // TODO: add more views here

View file

@ -765,7 +765,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: `remove with volumes`, Other: `remove with volumes`,
}, },
&i18n.Message{ &i18n.Message{
ID: "mustForceToRemove", ID: "mustForceToRemoveContainer",
Other: "You cannot remove a running container unless you force it. Do you want to force it?", Other: "You cannot remove a running container unless you force it. Do you want to force it?",
}, },
&i18n.Message{ &i18n.Message{
@ -820,5 +820,29 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "NoImages", ID: "NoImages",
Other: "No images", Other: "No images",
}, },
&i18n.Message{
ID: "removeImage",
Other: "remove image",
},
&i18n.Message{
ID: "removeWithoutPrune",
Other: "remove without deleting untagged parents",
},
&i18n.Message{
ID: "mustForceToRemoveImage",
Other: "Regular image delete not allowed. Do you want to force remove this image?",
},
&i18n.Message{
ID: "pruneImages",
Other: "prune unused images",
},
&i18n.Message{
ID: "confirmPruneImages",
Other: "Are you sure you want to prune all unused images?",
},
&i18n.Message{
ID: "PruningStatus",
Other: "pruning",
},
) )
} }