mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
support pruning images
This commit is contained in:
parent
4fe26f4f88
commit
15bd991560
7 changed files with 128 additions and 67 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||
"github.com/jesseduffield/lazydocker/pkg/i18n"
|
||||
|
|
@ -84,9 +85,12 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
|
|||
name = tags[0]
|
||||
}
|
||||
|
||||
nameParts := strings.Split(name, ":")
|
||||
|
||||
ownImages[i] = &Image{
|
||||
ID: image.ID,
|
||||
Name: name,
|
||||
Name: nameParts[0],
|
||||
Tag: nameParts[1],
|
||||
Image: image,
|
||||
Client: c.Client,
|
||||
OSCommand: c.OSCommand,
|
||||
|
|
@ -96,3 +100,9 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
|
|||
|
||||
return ownImages, nil
|
||||
}
|
||||
|
||||
// PruneImages prunes images
|
||||
func (c *DockerCommand) PruneImages() error {
|
||||
_, err := c.Client.ImagesPrune(context.Background(), filters.Args{})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/fatih/color"
|
||||
|
|
@ -11,6 +13,7 @@ import (
|
|||
// Image : A docker Image
|
||||
type Image struct {
|
||||
Name string
|
||||
Tag string
|
||||
ID string
|
||||
Image types.ImageSummary
|
||||
Client *client.Client
|
||||
|
|
@ -20,5 +23,14 @@ type Image struct {
|
|||
|
||||
// GetDisplayStrings returns the display string of Image
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ func (gui *Gui) handleContainersNextContext(g *gocui.Gui, v *gocui.View) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type removeOption struct {
|
||||
type removeContainerOption struct {
|
||||
description string
|
||||
command string
|
||||
configOptions types.ContainerRemoveOptions
|
||||
|
|
@ -301,7 +301,7 @@ type removeOption struct {
|
|||
}
|
||||
|
||||
// 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)}
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
options := []*removeOption{
|
||||
options := []*removeContainerOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("remove"),
|
||||
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
|
||||
if xerrors.As(cerr, &originalErr) {
|
||||
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
|
||||
if err := container.Remove(configOptions); err != nil {
|
||||
return err
|
||||
|
|
@ -348,7 +348,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
}, nil)
|
||||
}
|
||||
} else {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
return gui.createErrorPanel(gui.g, cerr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||
"images": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*2 - 1
|
||||
}
|
||||
|
||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||
|
|
@ -206,67 +208,66 @@ func (gui *Gui) handleImagesNextContext(g *gocui.Gui, v *gocui.View) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// type removeOption struct {
|
||||
// description string
|
||||
// command string
|
||||
// configOptions types.ImageRemoveOptions
|
||||
// runCommand bool
|
||||
// }
|
||||
type removeImageOption struct {
|
||||
description string
|
||||
command string
|
||||
configOptions types.ImageRemoveOptions
|
||||
runCommand bool
|
||||
}
|
||||
|
||||
// // GetDisplayStrings is a function.
|
||||
// func (r *removeOption) GetDisplayStrings(isFocused bool) []string {
|
||||
// return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
// }
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *removeImageOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
// func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
// Image, err := gui.getSelectedImage(g)
|
||||
// if err != nil {
|
||||
// return nil
|
||||
// }
|
||||
func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
Image, err := gui.getSelectedImage(g)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// options := []*removeOption{
|
||||
// {
|
||||
// description: gui.Tr.SLocalize("remove"),
|
||||
// command: "docker rm " + Image.ID[1:10],
|
||||
// configOptions: types.ImageRemoveOptions{},
|
||||
// runCommand: true,
|
||||
// },
|
||||
// {
|
||||
// description: gui.Tr.SLocalize("removeWithVolumes"),
|
||||
// command: "docker rm --volumes " + Image.ID[1:10],
|
||||
// configOptions: types.ImageRemoveOptions{RemoveVolumes: true},
|
||||
// runCommand: true,
|
||||
// },
|
||||
// {
|
||||
// description: gui.Tr.SLocalize("cancel"),
|
||||
// runCommand: false,
|
||||
// },
|
||||
// }
|
||||
options := []*removeImageOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("remove"),
|
||||
command: "docker image rm " + Image.ID[1:20],
|
||||
configOptions: types.ImageRemoveOptions{PruneChildren: true},
|
||||
runCommand: true,
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("removeWithoutPrune"),
|
||||
command: "docker image rm --no-prune " + Image.ID[1:20],
|
||||
configOptions: types.ImageRemoveOptions{PruneChildren: false},
|
||||
runCommand: true,
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
runCommand: false,
|
||||
},
|
||||
}
|
||||
|
||||
// handleMenuPress := func(index int) error {
|
||||
// if !options[index].runCommand {
|
||||
// return nil
|
||||
// }
|
||||
// configOptions := options[index].configOptions
|
||||
// if cerr := Image.Remove(configOptions); cerr != nil {
|
||||
// var originalErr commands.ComplexError
|
||||
// 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())
|
||||
// }
|
||||
// }
|
||||
handleMenuPress := func(index int) error {
|
||||
if !options[index].runCommand {
|
||||
return nil
|
||||
}
|
||||
configOptions := options[index].configOptions
|
||||
if cerr := Image.Remove(configOptions); cerr != nil {
|
||||
return gui.createErrorPanel(gui.g, cerr.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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||
Handler: gui.handleImagesNextContext,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -765,7 +765,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||
Other: `remove with volumes`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "mustForceToRemove",
|
||||
ID: "mustForceToRemoveContainer",
|
||||
Other: "You cannot remove a running container unless you force it. Do you want to force it?",
|
||||
},
|
||||
&i18n.Message{
|
||||
|
|
@ -820,5 +820,29 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||
ID: "NoImages",
|
||||
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",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue