From 64297ad1d54777a7a96353a0b515705dfdab6d35 Mon Sep 17 00:00:00 2001 From: christophe-duc Date: Thu, 8 Jan 2026 13:42:54 -0400 Subject: [PATCH] added remove capabilities for pod --- pkg/commands/pod.go | 20 +++++++++++++++++ pkg/commands/runtime.go | 1 + pkg/commands/runtime_libpod.go | 10 +++++++++ pkg/commands/runtime_libpod_stub.go | 4 ++++ pkg/commands/runtime_mock.go | 5 +++++ pkg/commands/runtime_socket.go | 7 ++++++ pkg/gui/containers_panel.go | 33 ++++++++++++++++++++++++++++- 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pkg/commands/pod.go b/pkg/commands/pod.go index f10c822d..75d075d5 100644 --- a/pkg/commands/pod.go +++ b/pkg/commands/pod.go @@ -3,10 +3,12 @@ package commands import ( "context" "fmt" + "strings" "time" "github.com/sasha-s/go-deadlock" "github.com/sirupsen/logrus" + "golang.org/x/xerrors" ) // Pod represents a Podman pod with its containers. @@ -69,6 +71,24 @@ func (p *Pod) Unpause() error { return p.Runtime.UnpausePod(ctx, p.ID) } +// Remove removes the pod +func (p *Pod) Remove(force bool) error { + p.Log.Warn(fmt.Sprintf("removing pod %s", p.Name)) + ctx := context.Background() + if err := p.Runtime.RemovePod(ctx, p.ID, force); err != nil { + if strings.Contains(err.Error(), "pod is running") || + strings.Contains(err.Error(), "stop the pod before attempting removal") { + return ComplexError{ + Code: MustStopContainer, + Message: err.Error(), + frame: xerrors.Caller(1), + } + } + return err + } + return nil +} + // HasContainers returns true if the pod has non-infra containers. func (p *Pod) HasContainers() bool { return len(p.Containers) > 0 diff --git a/pkg/commands/runtime.go b/pkg/commands/runtime.go index dddbe4d8..9a789212 100644 --- a/pkg/commands/runtime.go +++ b/pkg/commands/runtime.go @@ -45,6 +45,7 @@ type ContainerRuntime interface { StopPod(ctx context.Context, id string, timeout *int) error PausePod(ctx context.Context, id string) error UnpausePod(ctx context.Context, id string) error + RemovePod(ctx context.Context, id string, force bool) error // Events streams container/image/volume/network events Events(ctx context.Context) (<-chan Event, <-chan error) diff --git a/pkg/commands/runtime_libpod.go b/pkg/commands/runtime_libpod.go index 86db74fe..a4b0c3a4 100644 --- a/pkg/commands/runtime_libpod.go +++ b/pkg/commands/runtime_libpod.go @@ -493,6 +493,16 @@ func (r *LibpodRuntime) UnpausePod(ctx context.Context, id string) error { return err } +// RemovePod removes a pod from the system. +func (r *LibpodRuntime) RemovePod(ctx context.Context, id string, force bool) error { + pod, err := r.runtime.LookupPod(id) + if err != nil { + return err + } + _, err = r.runtime.RemovePod(ctx, pod, true, force, 0) + return err +} + // Events streams container runtime events. // For libpod, we use a polling approach since direct event streaming requires more setup. func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { diff --git a/pkg/commands/runtime_libpod_stub.go b/pkg/commands/runtime_libpod_stub.go index 0896c2be..80c8737a 100644 --- a/pkg/commands/runtime_libpod_stub.go +++ b/pkg/commands/runtime_libpod_stub.go @@ -165,6 +165,10 @@ func (r *LibpodRuntime) UnpausePod(ctx context.Context, id string) error { return ErrLibpodNotAvailable } +func (r *LibpodRuntime) RemovePod(ctx context.Context, id string, force bool) error { + return ErrLibpodNotAvailable +} + // Events returns an error channel on non-Linux platforms. func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { eventsChan := make(chan Event) diff --git a/pkg/commands/runtime_mock.go b/pkg/commands/runtime_mock.go index dc96613a..1c189240 100644 --- a/pkg/commands/runtime_mock.go +++ b/pkg/commands/runtime_mock.go @@ -309,6 +309,11 @@ func (m *MockRuntime) UnpausePod(ctx context.Context, id string) error { return ErrMockNotImplemented } +func (m *MockRuntime) RemovePod(ctx context.Context, id string, force bool) error { + m.recordCall("RemovePod", id, force) + return ErrMockNotImplemented +} + // Events func (m *MockRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { diff --git a/pkg/commands/runtime_socket.go b/pkg/commands/runtime_socket.go index 33018109..1ef3bccf 100644 --- a/pkg/commands/runtime_socket.go +++ b/pkg/commands/runtime_socket.go @@ -374,6 +374,13 @@ func (r *SocketRuntime) UnpausePod(ctx context.Context, id string) error { return err } +// RemovePod removes a pod from the system. +func (r *SocketRuntime) RemovePod(ctx context.Context, id string, force bool) error { + opts := &pods.RemoveOptions{Force: &force} + _, err := pods.Remove(r.conn, id, opts) + return err +} + // aggregatePodStats combines stats from all containers in a pod into a single entry. func aggregatePodStats(reports []*types.PodStatsReport) PodStatsEntry { if len(reports) == 0 { diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 8e470384..019b32a7 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -473,7 +473,38 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error { } if item.IsPod { - return gui.createErrorPanel("Remove not yet supported for pods") + pod := item.Pod + handleMenuPress := func(force bool) error { + return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { + if err := pod.Remove(force); err != nil { + if commands.HasErrorCode(err, commands.MustStopContainer) { + return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.MustForceToRemoveContainer, func(g *gocui.Gui, v *gocui.View) error { + return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { + return pod.Remove(true) + }) + }, nil) + } + return gui.createErrorPanel(err.Error()) + } + return nil + }) + } + + menuItems := []*types.MenuItem{ + { + LabelColumns: []string{gui.Tr.Remove, "podman pod rm " + pod.ID[1:10]}, + OnPress: func() error { return handleMenuPress(false) }, + }, + { + LabelColumns: []string{gui.Tr.ForceRemove, "podman pod rm -f " + pod.ID[1:10]}, + OnPress: func() error { return handleMenuPress(true) }, + }, + } + + return gui.Menu(CreateMenuOptions{ + Title: "", + Items: menuItems, + }) } ctr := item.Container