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/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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue