Merge pull request #7 from christophe-duc/feature/add_restart_pod

added restart functionality for pods
This commit is contained in:
Christophe 2026-01-07 23:59:22 -04:00 committed by GitHub
commit 70a9e480de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1042 additions and 879 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
package commands package commands
import ( import (
"context"
"fmt"
"time" "time"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
@ -32,6 +34,13 @@ func (p *Pod) State() string {
return p.Summary.Status return p.Summary.Status
} }
// Restart restarts the pod
func (p *Pod) Restart() error {
p.Log.Warn(fmt.Sprintf("restarting pod %s", p.Name))
ctx := context.Background()
return p.Runtime.RestartPod(ctx, p.ID, nil)
}
// HasContainers returns true if the pod has non-infra containers. // HasContainers returns true if the pod has non-infra containers.
func (p *Pod) HasContainers() bool { func (p *Pod) HasContainers() bool {
return len(p.Containers) > 0 return len(p.Containers) > 0

View file

@ -40,6 +40,7 @@ type ContainerRuntime interface {
// Pod operations // Pod operations
ListPods(ctx context.Context) ([]PodSummary, error) ListPods(ctx context.Context) ([]PodSummary, error)
PodStats(ctx context.Context, id string, stream bool) (<-chan PodStatsEntry, <-chan error) PodStats(ctx context.Context, id string, stream bool) (<-chan PodStatsEntry, <-chan error)
RestartPod(ctx context.Context, id string, timeout *int) error
// Events streams container/image/volume/network events // Events streams container/image/volume/network events
Events(ctx context.Context) (<-chan Event, <-chan error) Events(ctx context.Context) (<-chan Event, <-chan error)

View file

@ -375,7 +375,7 @@ func (r *LibpodRuntime) PodStats(ctx context.Context, id string, stream bool) (<
} }
// aggregatePodContainerStats collects and aggregates stats from all containers in a pod. // aggregatePodContainerStats collects and aggregates stats from all containers in a pod.
func (r *LibpodRuntime) aggregatePodContainerStats(ctx context.Context, pod *libpod.Pod) (PodStatsEntry, error) { func (r *LibpodRuntime) aggregatePodContainerStats(_ context.Context, pod *libpod.Pod) (PodStatsEntry, error) {
ctrs, err := pod.AllContainers() ctrs, err := pod.AllContainers()
if err != nil { if err != nil {
return PodStatsEntry{}, err return PodStatsEntry{}, err
@ -438,6 +438,16 @@ func (r *LibpodRuntime) aggregatePodContainerStats(ctx context.Context, pod *lib
return entry, nil return entry, nil
} }
// RestartPod restarts a pod.
func (r *LibpodRuntime) RestartPod(ctx context.Context, id string, timeout *int) error {
pod, err := r.runtime.LookupPod(id)
if err != nil {
return err
}
_, err = pod.Restart(ctx)
return err
}
// Events streams container runtime events. // Events streams container runtime events.
// For libpod, we use a polling approach since direct event streaming requires more setup. // 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) { func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {

View file

@ -145,6 +145,10 @@ func (r *LibpodRuntime) PodStats(ctx context.Context, id string, stream bool) (<
return statsChan, errChan return statsChan, errChan
} }
func (r *LibpodRuntime) RestartPod(ctx context.Context, id string, timeout *int) error {
return ErrLibpodNotAvailable
}
// Events returns an error channel on non-Linux platforms. // Events returns an error channel on non-Linux platforms.
func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {
eventsChan := make(chan Event) eventsChan := make(chan Event)

View file

@ -40,8 +40,9 @@ type MockRuntime struct {
PruneNetworksFunc func(ctx context.Context) error PruneNetworksFunc func(ctx context.Context) error
// Pod operation mocks // Pod operation mocks
ListPodsFunc func(ctx context.Context) ([]PodSummary, error) ListPodsFunc func(ctx context.Context) ([]PodSummary, error)
PodStatsFunc func(ctx context.Context, id string, stream bool) (<-chan PodStatsEntry, <-chan error) PodStatsFunc func(ctx context.Context, id string, stream bool) (<-chan PodStatsEntry, <-chan error)
RestartPodFunc func(ctx context.Context, id string, timeout *int) error
// Event mock // Event mock
EventsFunc func(ctx context.Context) (<-chan Event, <-chan error) EventsFunc func(ctx context.Context) (<-chan Event, <-chan error)
@ -280,6 +281,14 @@ func (m *MockRuntime) PodStats(ctx context.Context, id string, stream bool) (<-c
return statsCh, errCh return statsCh, errCh
} }
func (m *MockRuntime) RestartPod(ctx context.Context, id string, timeout *int) error {
m.recordCall("RestartPod", id, timeout)
if m.RestartPodFunc != nil {
return m.RestartPodFunc(ctx, id, timeout)
}
return ErrMockNotImplemented
}
// Events // Events
func (m *MockRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) { func (m *MockRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {

View file

@ -340,6 +340,12 @@ func (r *SocketRuntime) PodStats(ctx context.Context, id string, stream bool) (<
return statsChan, errChan return statsChan, errChan
} }
// RestartPod restarts a pod.
func (r *SocketRuntime) RestartPod(ctx context.Context, id string, timeout *int) error {
_, err := pods.Restart(r.conn, id, nil)
return err
}
// aggregatePodStats combines stats from all containers in a pod into a single entry. // aggregatePodStats combines stats from all containers in a pod into a single entry.
func aggregatePodStats(reports []*types.PodStatsReport) PodStatsEntry { func aggregatePodStats(reports []*types.PodStatsReport) PodStatsEntry {
if len(reports) == 0 { if len(reports) == 0 {
@ -405,30 +411,30 @@ func aggregatePodStats(reports []*types.PodStatsReport) PodStatsEntry {
func parsePercentage(s string) float64 { func parsePercentage(s string) float64 {
s = strings.TrimSuffix(strings.TrimSpace(s), "%") s = strings.TrimSuffix(strings.TrimSpace(s), "%")
var val float64 var val float64
fmt.Sscanf(s, "%f", &val) _, _ = fmt.Sscanf(s, "%f", &val)
return val return val
} }
// parseMemoryBytes parses memory usage string like "1000000 / 4000000" into usage and limit. // parseMemoryBytes parses memory usage string like "1000000 / 4000000" into usage and limit.
func parseMemoryBytes(s string) (usage, limit uint64) { func parseMemoryBytes(s string) (uint64, uint64) {
parts := strings.Split(s, "/") parts := strings.Split(s, "/")
if len(parts) != 2 { if len(parts) != 2 {
return 0, 0 return 0, 0
} }
usage = parseByteValue(strings.TrimSpace(parts[0])) usage := parseByteValue(strings.TrimSpace(parts[0]))
limit = parseByteValue(strings.TrimSpace(parts[1])) limit := parseByteValue(strings.TrimSpace(parts[1]))
return return usage, limit
} }
// parseIOBytes parses I/O string like "1.5kB / 2.3kB" into input and output bytes. // parseIOBytes parses I/O string like "1.5kB / 2.3kB" into input and output bytes.
func parseIOBytes(s string) (input, output uint64) { func parseIOBytes(s string) (uint64, uint64) {
parts := strings.Split(s, "/") parts := strings.Split(s, "/")
if len(parts) != 2 { if len(parts) != 2 {
return 0, 0 return 0, 0
} }
input = parseByteValue(strings.TrimSpace(parts[0])) input := parseByteValue(strings.TrimSpace(parts[0]))
output = parseByteValue(strings.TrimSpace(parts[1])) output := parseByteValue(strings.TrimSpace(parts[1]))
return return input, output
} }
// parseByteValue parses a byte value string with optional unit (e.g., "1.5kB", "10MB", "1000000"). // parseByteValue parses a byte value string with optional unit (e.g., "1.5kB", "10MB", "1000000").
@ -469,7 +475,7 @@ func parseByteValue(s string) uint64 {
func parseUint(s string) uint64 { func parseUint(s string) uint64 {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
var val uint64 var val uint64
fmt.Sscanf(s, "%d", &val) _, _ = fmt.Sscanf(s, "%d", &val)
return val return val
} }

View file

@ -568,7 +568,12 @@ func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
} }
if item.IsPod { if item.IsPod {
return gui.createErrorPanel("Restart not yet supported for pods") return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := item.Pod.Restart(); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
} }
ctr := item.Container ctr := item.Container