Merge pull request #1 from christophe-duc/feature/add_pod_support

feature: add pod support in the container pane
This commit is contained in:
Christophe 2026-01-07 18:41:08 -04:00 committed by GitHub
commit 0730120daa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 2277 additions and 625 deletions

View file

@ -55,11 +55,13 @@ pkg/
- `pkg/commands/runtime_socket.go` - Socket mode implementation using Podman REST API bindings
- `pkg/commands/runtime_libpod.go` - Direct libpod implementation (Linux+CGO only)
- `pkg/commands/runtime_libpod_stub.go` - Stub for non-Linux platforms
- `pkg/commands/runtime_types.go` - Custom types (ContainerSummary, ImageSummary, etc.)
- `pkg/commands/runtime_types.go` - Custom types (ContainerSummary, ImageSummary, PodSummary, etc.)
**Podman integration:**
- `pkg/commands/podman.go` - Main client connection, auto-detection, and initialization
- `pkg/commands/container.go` - Container wrapper operations
- `pkg/commands/pod.go` - Pod wrapper with state and container count methods
- `pkg/commands/container_list_item.go` - Unified wrapper for pods/containers in list view
- `pkg/commands/image.go` - Image wrapper operations
- `pkg/commands/volume.go` - Volume wrapper operations
- `pkg/commands/network.go` - Network wrapper operations
@ -102,6 +104,9 @@ ListContainers() / InspectContainer() / ContainerStats()
StartContainer() / StopContainer() / PauseContainer() / UnpauseContainer()
RestartContainer() / RemoveContainer() / PruneContainers() / ContainerTop()
// Pod operations
ListPods() - Returns all pods with their metadata
// Image operations
ListImages() / InspectImage() / ImageHistory() / RemoveImage() / PruneImages()

File diff suppressed because it is too large Load diff

4
go.mod
View file

@ -7,7 +7,6 @@ require (
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/containers/podman/v5 v5.7.1
github.com/docker/docker v28.5.1+incompatible
github.com/fatih/color v1.15.0
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.5.0
@ -27,6 +26,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stretchr/testify v1.11.1
go.podman.io/common v0.66.1
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)
@ -63,6 +63,7 @@ require (
github.com/disiqueira/gotree/v3 v3.0.2 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.5.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 // indirect
@ -155,7 +156,6 @@ require (
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.podman.io/common v0.66.1 // indirect
go.podman.io/image/v5 v5.38.0 // indirect
go.podman.io/storage v1.61.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect

View file

@ -0,0 +1,84 @@
package commands
// ContainerListItem represents either a pod or a container in the unified list view.
// This allows the container panel to display both pods and containers.
type ContainerListItem struct {
IsPod bool
Pod *Pod // Set if IsPod is true
Container *Container // Set if IsPod is false
Indent int // 0 for pods/standalone containers, 2 for containers in pods
}
// ID returns the unique ID for the item.
func (c *ContainerListItem) ID() string {
if c.IsPod && c.Pod != nil {
return c.Pod.ID
}
if c.Container != nil {
return c.Container.ID
}
return ""
}
// Name returns the display name for the item.
func (c *ContainerListItem) Name() string {
if c.IsPod && c.Pod != nil {
return c.Pod.Name
}
if c.Container != nil {
return c.Container.Name
}
return ""
}
// State returns the state for the item.
func (c *ContainerListItem) State() string {
if c.IsPod && c.Pod != nil {
return c.Pod.State()
}
if c.Container != nil {
return c.Container.Summary.State
}
return ""
}
// GetContainers returns the containers if this is a pod, nil otherwise.
func (c *ContainerListItem) GetContainers() []*Container {
if c.IsPod && c.Pod != nil {
return c.Pod.Containers
}
return nil
}
// IsInPod returns true if this is a container that belongs to a pod.
func (c *ContainerListItem) IsInPod() bool {
if c.IsPod {
return false
}
if c.Container != nil {
return c.Container.Summary.Pod != ""
}
return false
}
// PodID returns the pod ID if this container is in a pod, empty string otherwise.
func (c *ContainerListItem) PodID() string {
if c.IsPod && c.Pod != nil {
return c.Pod.ID
}
if c.Container != nil {
return c.Container.Summary.Pod
}
return ""
}
// PodName returns the pod name if this container is in a pod, empty string otherwise.
func (c *ContainerListItem) PodName() string {
if c.IsPod && c.Pod != nil {
return c.Pod.Name
}
if c.Container != nil {
return c.Container.Summary.PodName
}
return ""
}

36
pkg/commands/pod.go Normal file
View file

@ -0,0 +1,36 @@
package commands
import (
"github.com/sirupsen/logrus"
)
// Pod represents a Podman pod with its containers.
type Pod struct {
ID string
Name string
Summary PodSummary
Containers []*Container // Non-infra containers in this pod
OSCommand *OSCommand
Log *logrus.Entry
}
// State returns the pod state.
func (p *Pod) State() string {
return p.Summary.Status
}
// HasContainers returns true if the pod has non-infra containers.
func (p *Pod) HasContainers() bool {
return len(p.Containers) > 0
}
// GetRunningContainerCount returns the number of running containers in the pod.
func (p *Pod) GetRunningContainerCount() int {
count := 0
for _, c := range p.Containers {
if c.Summary.State == "running" {
count++
}
}
return count
}

View file

@ -7,6 +7,7 @@ import (
ogLog "log"
"os"
"os/exec"
"sort"
"strings"
"sync"
"time"
@ -347,15 +348,35 @@ func calculateMemoryPercentageFromEntry(stats ContainerStatsEntry) float64 {
return 0.0
}
func (c *PodmanCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container) ([]*Container, []*Service, error) {
func (c *PodmanCommand) RefreshContainersAndServices(currentServices []*Service, currentItems []*ContainerListItem) ([]*ContainerListItem, []*Service, error) {
c.ServiceMutex.Lock()
defer c.ServiceMutex.Unlock()
// Extract existing containers from current items
var currentContainers []*Container
for _, item := range currentItems {
if !item.IsPod && item.Container != nil {
currentContainers = append(currentContainers, item.Container)
}
}
containers, err := c.GetContainers(currentContainers)
if err != nil {
return nil, nil, err
}
// Get pods
ctx := context.Background()
podSummaries, err := c.Runtime.ListPods(ctx)
if err != nil {
// Don't fail if pods can't be listed, just log and continue without pods
c.Log.Warnf("Failed to list pods: %v", err)
podSummaries = nil
}
// Build the unified list
items := c.buildContainerListItems(containers, podSummaries)
var services []*Service
// we only need to get these services once because they won't change in the runtime of the program
if currentServices != nil {
@ -367,9 +388,98 @@ func (c *PodmanCommand) RefreshContainersAndServices(currentServices []*Service,
}
}
c.assignContainersToServices(containers, services)
// Extract just containers for service assignment
var containersForServices []*Container
for _, item := range items {
if !item.IsPod && item.Container != nil {
containersForServices = append(containersForServices, item.Container)
}
}
c.assignContainersToServices(containersForServices, services)
return containers, services, nil
return items, services, nil
}
// buildContainerListItems creates a unified list of pods and containers
func (c *PodmanCommand) buildContainerListItems(containers []*Container, podSummaries []PodSummary) []*ContainerListItem {
var items []*ContainerListItem
// Create a map of pod ID -> pod summary
podMap := make(map[string]PodSummary)
for _, ps := range podSummaries {
podMap[ps.ID] = ps
}
// Create a map of pod ID -> containers in that pod
podContainers := make(map[string][]*Container)
var standaloneContainers []*Container
for _, ctr := range containers {
// Skip infra containers
if ctr.Summary.IsInfra {
continue
}
if ctr.Summary.Pod != "" {
podContainers[ctr.Summary.Pod] = append(podContainers[ctr.Summary.Pod], ctr)
} else {
standaloneContainers = append(standaloneContainers, ctr)
}
}
// Sort pod IDs for deterministic ordering
podIDs := make([]string, 0, len(podMap))
for podID := range podMap {
podIDs = append(podIDs, podID)
}
sort.Slice(podIDs, func(i, j int) bool {
return podMap[podIDs[i]].Name < podMap[podIDs[j]].Name
})
// Add pods and their containers
for _, podID := range podIDs {
ps := podMap[podID]
// Create pod object
pod := &Pod{
ID: ps.ID,
Name: ps.Name,
Summary: ps,
Containers: podContainers[podID],
OSCommand: c.OSCommand,
Log: c.Log,
}
// Add pod item
items = append(items, &ContainerListItem{
IsPod: true,
Pod: pod,
Indent: 0,
})
// Add containers in this pod with indent
for _, ctr := range podContainers[podID] {
// Set pod name on container if not already set
if ctr.Summary.PodName == "" {
ctr.Summary.PodName = ps.Name
}
items = append(items, &ContainerListItem{
IsPod: false,
Container: ctr,
Indent: 2,
})
}
}
// Add standalone containers
for _, ctr := range standaloneContainers {
items = append(items, &ContainerListItem{
IsPod: false,
Container: ctr,
Indent: 0,
})
}
return items
}
func (c *PodmanCommand) assignContainersToServices(containers []*Container, services []*Service) {

View file

@ -37,6 +37,9 @@ type ContainerRuntime interface {
RemoveNetwork(ctx context.Context, name string) error
PruneNetworks(ctx context.Context) error
// Pod operations
ListPods(ctx context.Context) ([]PodSummary, error)
// Events streams container/image/volume/network events
Events(ctx context.Context) (<-chan Event, <-chan error)

View file

@ -146,7 +146,7 @@ func (r *LibpodRuntime) ContainerTop(ctx context.Context, id string) ([]string,
// Parse the result: first line is headers, rest are process rows
// Each line is space-separated fields
headers := splitFields(result[0])
var processes [][]string
processes := make([][]string, 0, len(result)-1)
for _, line := range result[1:] {
processes = append(processes, splitFields(line))
}
@ -309,6 +309,37 @@ func (r *LibpodRuntime) PruneNetworks(ctx context.Context) error {
return nil
}
// Pod operations
// ListPods returns all pods.
func (r *LibpodRuntime) ListPods(ctx context.Context) ([]PodSummary, error) {
pods, err := r.runtime.GetAllPods()
if err != nil {
return nil, err
}
result := make([]PodSummary, len(pods))
for i, pod := range pods {
status, err := pod.GetPodStatus()
if err != nil {
status = "unknown"
}
config, configErr := pod.Config()
infraID := ""
if configErr == nil && config != nil {
infraID = config.InfraContainerID
}
result[i] = PodSummary{
ID: pod.ID(),
Name: pod.Name(),
Status: status,
Created: pod.CreatedTime(),
InfraID: infraID,
Labels: pod.Labels(),
}
}
return result, nil
}
// 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) {
@ -361,6 +392,14 @@ func convertLibpodContainerList(ctrs []*libpod.Container) ([]ContainerSummary, e
if len(config.Command) > 0 {
command = config.Command[0]
}
// Get pod name if container is in a pod
podName := ""
if podID := ctr.PodID(); podID != "" {
// Try to get pod name from config labels or leave empty
if pn, ok := config.Labels["io.kubernetes.pod.name"]; ok {
podName = pn
}
}
result[i] = ContainerSummary{
ID: ctr.ID(),
Names: []string{ctr.Name()},
@ -372,6 +411,8 @@ func convertLibpodContainerList(ctrs []*libpod.Container) ([]ContainerSummary, e
Status: state.String(),
Labels: config.Labels,
Pod: ctr.PodID(),
PodName: podName,
IsInfra: ctr.IsInfra(),
}
}
return result, nil

View file

@ -128,6 +128,12 @@ func (r *LibpodRuntime) PruneNetworks(ctx context.Context) error {
return ErrLibpodNotAvailable
}
// Pod operations - all return ErrLibpodNotAvailable
func (r *LibpodRuntime) ListPods(ctx context.Context) ([]PodSummary, error) {
return nil, ErrLibpodNotAvailable
}
// Events returns an error channel on non-Linux platforms.
func (r *LibpodRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {
errChan := make(chan error, 1)

View file

@ -39,6 +39,9 @@ type MockRuntime struct {
RemoveNetworkFunc func(ctx context.Context, name string) error
PruneNetworksFunc func(ctx context.Context) error
// Pod operation mocks
ListPodsFunc func(ctx context.Context) ([]PodSummary, error)
// Event mock
EventsFunc func(ctx context.Context) (<-chan Event, <-chan error)
@ -251,6 +254,16 @@ func (m *MockRuntime) PruneNetworks(ctx context.Context) error {
return ErrMockNotImplemented
}
// Pod operations
func (m *MockRuntime) ListPods(ctx context.Context) ([]PodSummary, error) {
m.recordCall("ListPods")
if m.ListPodsFunc != nil {
return m.ListPodsFunc(ctx)
}
return nil, ErrMockNotImplemented
}
// Events
func (m *MockRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {

View file

@ -11,6 +11,7 @@ import (
"github.com/containers/podman/v5/pkg/bindings/containers"
"github.com/containers/podman/v5/pkg/bindings/images"
"github.com/containers/podman/v5/pkg/bindings/network"
"github.com/containers/podman/v5/pkg/bindings/pods"
"github.com/containers/podman/v5/pkg/bindings/system"
"github.com/containers/podman/v5/pkg/bindings/volumes"
"github.com/containers/podman/v5/pkg/domain/entities"
@ -275,6 +276,27 @@ func (r *SocketRuntime) PruneNetworks(ctx context.Context) error {
return err
}
// ListPods returns all pods using the pods bindings API.
func (r *SocketRuntime) ListPods(ctx context.Context) ([]PodSummary, error) {
podList, err := pods.List(r.conn, nil)
if err != nil {
return nil, err
}
result := make([]PodSummary, len(podList))
for i, p := range podList {
result[i] = PodSummary{
ID: p.Id,
Name: p.Name,
Status: p.Status,
Created: p.Created,
InfraID: p.InfraId,
Labels: p.Labels,
}
}
return result, nil
}
// Events streams container runtime events.
func (r *SocketRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) {
eventChan := make(chan Event)
@ -346,6 +368,7 @@ func convertPodmanContainerList(podmanContainers []entities.ListContainer) []Con
SizeRootFs: sizeRootFs,
Pod: c.Pod,
PodName: c.PodName,
IsInfra: c.IsInfra,
}
}
return result

View file

@ -20,6 +20,7 @@ type ContainerSummary struct {
// Podman-specific fields
Pod string
PodName string
IsInfra bool
}
// PortMapping represents a container port mapping.
@ -424,3 +425,13 @@ type EventActor struct {
ID string
Attributes map[string]string
}
// PodSummary provides runtime-agnostic pod information.
type PodSummary struct {
ID string
Name string
Status string
Created time.Time
InfraID string
Labels map[string]string
}

View file

@ -142,3 +142,75 @@ func (gui *Gui) writeContainerLogs(ctr *commands.Container, ctx context.Context,
return err
}
}
// Pod logs rendering
func (gui *Gui) renderPodLogsToMain(pod *commands.Pod) tasks.TaskFunc {
return gui.NewTickerTask(TickerTaskOpts{
Func: func(ctx context.Context, notifyStopped chan struct{}) {
gui.renderPodLogsToMainAux(pod, ctx, notifyStopped)
},
Duration: time.Millisecond * 200,
Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: true,
})
}
func (gui *Gui) renderPodLogsToMainAux(pod *commands.Pod, ctx context.Context, notifyStopped chan struct{}) {
gui.clearMainView()
defer func() {
notifyStopped <- struct{}{}
}()
mainView := gui.Views.Main
if err := gui.writePodLogs(pod, ctx, mainView); err != nil {
gui.Log.Error(err)
}
// Wait for context cancellation
<-ctx.Done()
}
func (gui *Gui) writePodLogs(pod *commands.Pod, ctx context.Context, writer io.Writer) error {
// Build podman pod logs command
// Note: --color is used to distinguish output from different containers in the pod
args := []string{"pod", "logs", "--follow"}
if gui.Config.UserConfig.Logs.Timestamps {
args = append(args, "--timestamps")
}
if gui.Config.UserConfig.Logs.Since != "" {
args = append(args, "--since", gui.Config.UserConfig.Logs.Since)
}
if gui.Config.UserConfig.Logs.Tail != "" {
args = append(args, "--tail", gui.Config.UserConfig.Logs.Tail)
}
args = append(args, pod.Name)
cmd := pod.OSCommand.NewCmd("podman", args...)
cmd.Stdout = writer
cmd.Stderr = writer
if err := cmd.Start(); err != nil {
gui.Log.Error(err)
return err
}
// Wait for context cancellation or command completion
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-ctx.Done():
if err := pod.OSCommand.Kill(cmd); err != nil {
gui.Log.Warn(err)
}
return nil
case err := <-done:
return err
}
}

View file

@ -18,7 +18,7 @@ import (
"github.com/samber/lo"
)
func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container] {
func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.ContainerListItem] {
// Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context.
isStandaloneContainer := func(container *commands.Container) bool {
if container.OneOff || container.ServiceName == "" {
@ -30,58 +30,60 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
})
}
return &panels.SideListPanel[*commands.Container]{
ContextState: &panels.ContextState[*commands.Container]{
GetMainTabs: func() []panels.MainTab[*commands.Container] {
return []panels.MainTab[*commands.Container]{
return &panels.SideListPanel[*commands.ContainerListItem]{
ContextState: &panels.ContextState[*commands.ContainerListItem]{
GetMainTabs: func() []panels.MainTab[*commands.ContainerListItem] {
return []panels.MainTab[*commands.ContainerListItem]{
{
Key: "logs",
Title: gui.Tr.LogsTitle,
Render: gui.renderContainerLogsToMain,
Render: gui.renderContainerListItemLogs,
},
{
Key: "stats",
Title: gui.Tr.StatsTitle,
Render: gui.renderContainerStats,
Render: gui.renderContainerListItemStats,
},
{
Key: "env",
Title: gui.Tr.EnvTitle,
Render: gui.renderContainerEnv,
Render: gui.renderContainerListItemEnv,
},
{
Key: "config",
Title: gui.Tr.ConfigTitle,
Render: gui.renderContainerConfig,
Render: gui.renderContainerListItemConfig,
},
{
Key: "top",
Title: gui.Tr.TopTitle,
Render: gui.renderContainerTop,
Render: gui.renderContainerListItemTop,
},
}
},
GetItemContextCacheKey: func(container *commands.Container) string {
// Including the container state in the cache key so that if the container
// restarts we re-read the logs. In the past we've had some glitchiness
// where a container restarts but the new logs don't get read.
// Note that this might be jarring if we have a lot of logs and the container
// restarts a lot, so let's keep an eye on it.
return "containers-" + container.ID + "-" + container.Summary.State
GetItemContextCacheKey: func(item *commands.ContainerListItem) string {
// Including the state in the cache key so that if the container/pod
// restarts we re-read the logs.
return "containers-" + item.ID() + "-" + item.State()
},
},
ListPanel: panels.ListPanel[*commands.Container]{
List: panels.NewFilteredList[*commands.Container](),
ListPanel: panels.ListPanel[*commands.ContainerListItem]{
List: panels.NewFilteredList[*commands.ContainerListItem](),
View: gui.Views.Containers,
},
NoItemsMessage: gui.Tr.NoContainers,
Gui: gui.intoInterface(),
// sortedContainers returns containers sorted by state if c.SortContainersByState is true (follows 1- running, 2- exited, 3- created)
// and sorted by name if c.SortContainersByState is false
Sort: func(a *commands.Container, b *commands.Container) bool {
return sortContainers(a, b, gui.Config.UserConfig.Gui.LegacySortContainers)
// Sort items: pods first (with their containers grouped), then standalone containers
Sort: func(a *commands.ContainerListItem, b *commands.ContainerListItem) bool {
return sortContainerListItems(a, b, gui.Config.UserConfig.Gui.LegacySortContainers)
},
Filter: func(container *commands.Container) bool {
Filter: func(item *commands.ContainerListItem) bool {
// Pods are always shown
if item.IsPod {
return true
}
container := item.Container
// Note that this is O(N*M) time complexity where N is the number of services
// and M is the number of containers. We expect N to be small but M may be large,
// so we will need to keep an eye on this.
@ -95,8 +97,8 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
return true
},
GetTableCells: func(container *commands.Container) []string {
return presentation.GetContainerDisplayStrings(&gui.Config.UserConfig.Gui, container)
GetTableCells: func(item *commands.ContainerListItem) []string {
return presentation.GetContainerListItemDisplayStrings(&gui.Config.UserConfig.Gui, item)
},
}
}
@ -121,6 +123,137 @@ func sortContainers(a *commands.Container, b *commands.Container, legacySort boo
return containerStates[a.Summary.State] < containerStates[b.Summary.State]
}
// sortContainerListItems sorts items to group pods with their containers.
// Order: pods first (sorted by state/name), then their containers indented,
// then standalone containers (sorted by state/name).
func sortContainerListItems(a *commands.ContainerListItem, b *commands.ContainerListItem, legacySort bool) bool {
// If both are in the same pod, sort by indent (pod first) then by name
if a.PodID() != "" && a.PodID() == b.PodID() {
// Pod comes before its containers
if a.IsPod && !b.IsPod {
return true
}
if !a.IsPod && b.IsPod {
return false
}
// Both are containers in the same pod, sort by name
return a.Name() < b.Name()
}
// Get the effective sort key (pod name for items in pods, own name for standalone)
aKey := a.Name()
bKey := b.Name()
if a.PodID() != "" && !a.IsPod {
aKey = a.PodName() + "\x00" + a.Name() // Sort after the pod
}
if b.PodID() != "" && !b.IsPod {
bKey = b.PodName() + "\x00" + b.Name()
}
if a.IsPod {
aKey = a.Name() + "\x00" // Pod sorts before its containers
}
if b.IsPod {
bKey = b.Name() + "\x00"
}
// Pods and their containers sort together, standalone containers at the end
aInPod := a.IsPod || a.PodID() != ""
bInPod := b.IsPod || b.PodID() != ""
if aInPod && !bInPod {
return true // Pods/pod containers before standalone
}
if !aInPod && bInPod {
return false
}
// Both in same category (pod-related or standalone)
if legacySort {
return aKey < bKey
}
// Sort by state, then by key
stateA := containerStates[a.State()]
stateB := containerStates[b.State()]
if stateA == stateB {
return aKey < bKey
}
return stateA < stateB
}
// Wrapper functions that delegate to container or pod rendering
func (gui *Gui) renderContainerListItemLogs(item *commands.ContainerListItem) tasks.TaskFunc {
if item.IsPod {
return gui.renderPodLogsToMain(item.Pod)
}
return gui.renderContainerLogsToMain(item.Container)
}
func (gui *Gui) renderContainerListItemStats(item *commands.ContainerListItem) tasks.TaskFunc {
if item.IsPod {
return gui.NewSimpleRenderStringTask(func() string {
return fmt.Sprintf("Pod: %s\nStatus: %s\nContainers: %d\n\nStats not available for pods. Select a container to view stats.",
item.Pod.Name, item.Pod.State(), len(item.Pod.Containers))
})
}
return gui.renderContainerStats(item.Container)
}
func (gui *Gui) renderContainerListItemEnv(item *commands.ContainerListItem) tasks.TaskFunc {
if item.IsPod {
return gui.NewSimpleRenderStringTask(func() string {
return "Environment variables not available for pods. Select a container to view environment."
})
}
return gui.renderContainerEnv(item.Container)
}
func (gui *Gui) renderContainerListItemConfig(item *commands.ContainerListItem) tasks.TaskFunc {
if item.IsPod {
return gui.renderPodConfig(item.Pod)
}
return gui.renderContainerConfig(item.Container)
}
func (gui *Gui) renderContainerListItemTop(item *commands.ContainerListItem) tasks.TaskFunc {
if item.IsPod {
return gui.NewSimpleRenderStringTask(func() string {
return "Process list not available for pods. Select a container to view processes."
})
}
return gui.renderContainerTop(item.Container)
}
func (gui *Gui) renderPodConfig(pod *commands.Pod) tasks.TaskFunc {
return gui.NewSimpleRenderStringTask(func() string {
padding := 10
output := ""
output += utils.ColoredString("Pod Information\n\n", color.FgCyan)
output += utils.WithPadding("ID: ", padding) + pod.ID + "\n"
output += utils.WithPadding("Name: ", padding) + pod.Name + "\n"
output += utils.WithPadding("Status: ", padding) + pod.State() + "\n"
output += utils.WithPadding("Created: ", padding) + pod.Summary.Created.Format(time.RFC3339) + "\n"
output += fmt.Sprintf("%s%d\n", utils.WithPadding("Containers: ", padding), len(pod.Containers))
if len(pod.Containers) > 0 {
output += "\n" + utils.ColoredString("Containers in this pod:\n", color.FgYellow)
for _, c := range pod.Containers {
stateColor := color.FgWhite
switch c.Summary.State {
case "running":
stateColor = color.FgGreen
case "exited":
stateColor = color.FgRed
}
output += fmt.Sprintf(" - %s (%s)\n", c.Name, utils.ColoredString(c.Summary.State, stateColor))
}
}
return output
})
}
func (gui *Gui) renderContainerEnv(container *commands.Container) tasks.TaskFunc {
return gui.NewSimpleRenderStringTask(func() string { return gui.containerEnv(container) })
}
@ -179,9 +312,9 @@ func (gui *Gui) containerConfigStr(container *commands.Container) string {
output += "\n"
for _, mount := range container.Details.Mounts {
if mount.Type == "volume" {
output += fmt.Sprintf("%s%s %s\n", strings.Repeat(" ", padding), utils.ColoredString(string(mount.Type)+":", color.FgYellow), mount.Name)
output += fmt.Sprintf("%s%s %s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Name)
} else {
output += fmt.Sprintf("%s%s %s:%s\n", strings.Repeat(" ", padding), utils.ColoredString(string(mount.Type)+":", color.FgYellow), mount.Source, mount.Destination)
output += fmt.Sprintf("%s%s %s:%s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Source, mount.Destination)
}
}
} else {
@ -302,11 +435,16 @@ func (gui *Gui) handleHideStoppedContainers(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
if item.IsPod {
return gui.createErrorPanel("Remove not yet supported for pods")
}
ctr := item.Container
handleMenuPress := func(force bool, removeVolumes bool) error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
if err := ctr.Remove(force, removeVolumes); err != nil {
@ -357,20 +495,29 @@ func (gui *Gui) PauseContainer(container *commands.Container) error {
}
func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
return gui.PauseContainer(ctr)
if item.IsPod {
return gui.createErrorPanel("Pause not yet supported for pods")
}
return gui.PauseContainer(item.Container)
}
func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
if item.IsPod {
return gui.createErrorPanel("Stop not yet supported for pods")
}
ctr := item.Container
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.StopContainer, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
if err := ctr.Stop(); err != nil {
@ -383,11 +530,16 @@ func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
if item.IsPod {
return gui.createErrorPanel("Restart not yet supported for pods")
}
ctr := item.Container
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := ctr.Restart(); err != nil {
return gui.createErrorPanel(err.Error())
@ -398,11 +550,16 @@ func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
if item.IsPod {
return gui.createErrorPanel("Attach not yet supported for pods")
}
ctr := item.Container
c, err := ctr.Attach()
if err != nil {
return gui.createErrorPanel(err.Error())
@ -424,23 +581,32 @@ func (gui *Gui) handlePruneContainers() error {
}
func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
gui.renderLogsToStdout(ctr)
if item.IsPod {
// TODO: implement pod logs to stdout
return gui.createErrorPanel("View logs (stdout) not yet supported for pods")
}
gui.renderLogsToStdout(item.Container)
return nil
}
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
return gui.containerExecShell(ctr)
if item.IsPod {
return gui.createErrorPanel("Exec shell not yet supported for pods. Select a container instead.")
}
return gui.containerExecShell(item.Container)
}
func (gui *Gui) containerExecShell(container *commands.Container) error {
@ -456,13 +622,17 @@ func (gui *Gui) containerExecShell(container *commands.Container) error {
}
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
if item.IsPod {
return gui.createErrorPanel("Custom commands not yet supported for pods. Select a container instead.")
}
commandObject := gui.PodmanCommand.NewCommandObject(commands.CommandObject{
Container: ctr,
Container: item.Container,
})
customCommands := gui.Config.UserConfig.CustomCommands.Containers
@ -473,9 +643,11 @@ func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error
func (gui *Gui) handleStopContainers() error {
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmStopContainers, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
for _, ctr := range gui.Panels.Containers.List.GetAllItems() {
if err := ctr.Stop(); err != nil {
gui.Log.Error(err)
for _, item := range gui.Panels.Containers.List.GetAllItems() {
if !item.IsPod && item.Container != nil {
if err := item.Container.Stop(); err != nil {
gui.Log.Error(err)
}
}
}
@ -487,9 +659,11 @@ func (gui *Gui) handleStopContainers() error {
func (gui *Gui) handleRemoveContainers() error {
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
for _, ctr := range gui.Panels.Containers.List.GetAllItems() {
if err := ctr.Remove(true, false); err != nil {
gui.Log.Error(err)
for _, item := range gui.Panels.Containers.List.GetAllItems() {
if !item.IsPod && item.Container != nil {
if err := item.Container.Remove(true, false); err != nil {
gui.Log.Error(err)
}
}
}
@ -522,12 +696,16 @@ func (gui *Gui) handleContainersBulkCommand(g *gocui.Gui, v *gocui.View) error {
// Open first port in browser
func (gui *Gui) handleContainersOpenInBrowserCommand(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
item, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}
return gui.openContainerInBrowser(ctr)
if item.IsPod {
return gui.createErrorPanel("Cannot open pod in browser. Select a container instead.")
}
return gui.openContainerInBrowser(item.Container)
}
func (gui *Gui) openContainerInBrowser(ctr *commands.Container) error {

View file

@ -48,7 +48,7 @@ type Gui struct {
type Panels struct {
Projects *panels.SideListPanel[*commands.Project]
Services *panels.SideListPanel[*commands.Service]
Containers *panels.SideListPanel[*commands.Container]
Containers *panels.SideListPanel[*commands.ContainerListItem]
Images *panels.SideListPanel[*commands.Image]
Volumes *panels.SideListPanel[*commands.Volume]
Networks *panels.SideListPanel[*commands.Network]
@ -290,7 +290,14 @@ func (gui *Gui) setPanels() {
}
func (gui *Gui) updateContainerDetails() error {
return gui.PodmanCommand.RefreshContainerDetails(gui.Panels.Containers.List.GetAllItems())
// Extract containers from ContainerListItems
var containers []*commands.Container
for _, item := range gui.Panels.Containers.List.GetAllItems() {
if !item.IsPod && item.Container != nil {
containers = append(containers, item.Container)
}
}
return gui.PodmanCommand.RefreshContainerDetails(containers)
}
func (gui *Gui) refresh() {
@ -469,9 +476,9 @@ func (gui *Gui) monitorContainerStats(ctx context.Context) {
case <-ctx.Done():
return
case <-ticker.C:
for _, container := range gui.Panels.Containers.List.GetAllItems() {
if !container.MonitoringStats {
go gui.PodmanCommand.CreateClientStatMonitor(container)
for _, item := range gui.Panels.Containers.List.GetAllItems() {
if !item.IsPod && item.Container != nil && !item.Container.MonitoringStats {
go gui.PodmanCommand.CreateClientStatMonitor(item.Container)
}
}
}

View file

@ -8,11 +8,11 @@ import (
"strings"
"time"
"github.com/fatih/color"
"github.com/jesseduffield/asciigraph"
"github.com/christophe-duc/lazypodman/pkg/commands"
"github.com/christophe-duc/lazypodman/pkg/config"
"github.com/christophe-duc/lazypodman/pkg/utils"
"github.com/fatih/color"
"github.com/jesseduffield/asciigraph"
"github.com/mcuadros/go-lookup"
"github.com/samber/lo"
)

View file

@ -24,6 +24,96 @@ func GetContainerDisplayStrings(guiConfig *config.GuiConfig, container *commands
}
}
// GetContainerListItemDisplayStrings returns display strings for a ContainerListItem (pod or container)
func GetContainerListItemDisplayStrings(guiConfig *config.GuiConfig, item *commands.ContainerListItem) []string {
if item.IsPod && item.Pod != nil {
return GetPodDisplayStrings(guiConfig, item.Pod)
}
if item.Container == nil {
// Return an empty row with the expected number of columns when container data is missing.
return []string{"", "", "", "", "", ""}
}
// Add indentation for containers in pods
strings := GetContainerDisplayStrings(guiConfig, item.Container)
if item.Indent > 0 {
strings[2] = fmt.Sprintf("%s%s", createIndent(item.Indent), strings[2])
}
return strings
}
// GetPodDisplayStrings returns display strings for a pod
func GetPodDisplayStrings(guiConfig *config.GuiConfig, pod *commands.Pod) []string {
return []string{
getPodDisplayStatus(guiConfig, pod),
"", // No substatus for pods
utils.ColoredString(pod.Name, color.FgCyan),
"", // No CPU% for pods
"", // No ports for pods
utils.ColoredString(fmt.Sprintf("(%d containers)", len(pod.Containers)), color.FgMagenta),
}
}
func getPodDisplayStatus(guiConfig *config.GuiConfig, pod *commands.Pod) string {
shortStatusMap := map[string]string{
"Running": "R",
"Degraded": "D",
"Exited": "X",
"Dead": "!",
"Created": "C",
}
iconStatusMap := map[string]rune{
"Running": '▶',
"Degraded": '◐',
"Exited": '',
"Dead": '!',
"Created": '+',
}
var podState string
switch guiConfig.ContainerStatusHealthStyle {
case "short":
if s, ok := shortStatusMap[pod.State()]; ok {
podState = s
} else {
podState = "?"
}
case "icon":
if r, ok := iconStatusMap[pod.State()]; ok {
podState = string(r)
} else {
podState = "?"
}
default:
podState = pod.State()
}
return utils.ColoredString(podState, getPodColor(pod))
}
func getPodColor(pod *commands.Pod) color.Attribute {
switch pod.State() {
case "Running":
return color.FgGreen
case "Degraded":
return color.FgYellow
case "Exited":
return color.FgRed
case "Dead":
return color.FgRed
case "Created":
return color.FgCyan
default:
return color.FgWhite
}
}
func createIndent(spaces int) string {
return fmt.Sprintf("%*s", spaces, "")
}
func displayContainerImage(container *commands.Container) string {
return strings.TrimPrefix(container.Summary.Image, "sha256:")
}

View file

@ -109,18 +109,17 @@ type TranslationSet struct {
FilterList string
OpenInBrowser string
SortContainersByState string
LogsTitle string
ConfigTitle string
EnvTitle string
ComposeConfigTitle string
StatsTitle string
CreditsTitle string
ContainerConfigTitle string
ContainerEnvTitle string
NothingToDisplay string
NoContainerForService string
CannotDisplayEnvVariables string
LogsTitle string
ConfigTitle string
EnvTitle string
ComposeConfigTitle string
StatsTitle string
CreditsTitle string
ContainerConfigTitle string
ContainerEnvTitle string
NothingToDisplay string
NoContainerForService string
CannotDisplayEnvVariables string
No string
Yes string

View file

@ -0,0 +1,342 @@
package pods
import (
"context"
"net/http"
"net/url"
"strings"
"github.com/containers/podman/v5/pkg/api/handlers"
"github.com/containers/podman/v5/pkg/bindings"
entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types"
"github.com/containers/podman/v5/pkg/errorhandling"
jsoniter "github.com/json-iterator/go"
)
func CreatePodFromSpec(ctx context.Context, spec *entitiesTypes.PodSpec) (*entitiesTypes.PodCreateReport, error) {
var pcr entitiesTypes.PodCreateReport
if spec == nil {
spec = new(entitiesTypes.PodSpec)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
specString, err := jsoniter.MarshalToString(spec.PodSpecGen)
if err != nil {
return nil, err
}
stringReader := strings.NewReader(specString)
response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/pods/create", nil, nil)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &pcr, response.Process(&pcr)
}
// Exists is a lightweight method to determine if a pod exists in local storage
func Exists(ctx context.Context, nameOrID string, _ *ExistsOptions) (bool, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return false, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
defer response.Body.Close()
return response.IsSuccess(), nil
}
// Inspect returns low-level information about the given pod.
func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*entitiesTypes.PodInspectReport, error) {
var report entitiesTypes.PodInspectReport
if options == nil {
options = new(InspectOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.Process(&report)
}
// Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter
// can be used to override SIGTERM.
func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entitiesTypes.PodKillReport, error) {
var report entitiesTypes.PodKillReport
if options == nil {
options = new(KillOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params, err := options.ToParams()
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Pause pauses all running containers in a given pod.
func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entitiesTypes.PodPauseReport, error) {
var report entitiesTypes.PodPauseReport
if options == nil {
options = new(PauseOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Prune by default removes all non-running pods in local storage.
// And with force set true removes all pods.
func Prune(ctx context.Context, options *PruneOptions) ([]*entitiesTypes.PodPruneReport, error) {
var reports []*entitiesTypes.PodPruneReport
if options == nil {
options = new(PruneOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/prune", nil, nil)
if err != nil {
return nil, err
}
defer response.Body.Close()
return reports, response.Process(&reports)
}
// List returns all pods in local storage. The optional filters parameter can
// be used to refine which pods should be listed.
func List(ctx context.Context, options *ListOptions) ([]*entitiesTypes.ListPodsReport, error) {
var podsReports []*entitiesTypes.ListPodsReport
if options == nil {
options = new(ListOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params, err := options.ToParams()
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/json", params, nil)
if err != nil {
return podsReports, err
}
defer response.Body.Close()
return podsReports, response.Process(&podsReports)
}
// Restart restarts all containers in a pod.
func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*entitiesTypes.PodRestartReport, error) {
var report entitiesTypes.PodRestartReport
if options == nil {
options = new(RestartOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Remove deletes a Pod from local storage. The optional force parameter denotes
// that the Pod can be removed even if in a running state.
func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entitiesTypes.PodRmReport, error) {
var report entitiesTypes.PodRmReport
if options == nil {
options = new(RemoveOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params, err := options.ToParams()
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.Process(&report)
}
// Start starts all containers in a pod.
func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entitiesTypes.PodStartReport, error) {
var report entitiesTypes.PodStartReport
if options == nil {
options = new(StartOptions)
}
_ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode == http.StatusNotModified {
report.Id = nameOrID
report.RawInput = nameOrID
return &report, nil
}
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Stop stops all containers in a Pod. The optional timeout parameter can be
// used to override the timeout before the container is killed.
func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entitiesTypes.PodStopReport, error) {
var report entitiesTypes.PodStopReport
if options == nil {
options = new(StopOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params, err := options.ToParams()
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode == http.StatusNotModified {
report.Id = nameOrID
return &report, nil
}
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Top gathers statistics about the running processes in a pod. The nameOrID can be a pod name
// or a partial/full ID. The descriptors allow for specifying which data to collect from each process.
func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, error) {
if options == nil {
options = new(TopOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
if descriptors := options.GetDescriptors(); len(descriptors) > 0 {
params.Set("ps_args", strings.Join(descriptors, ","))
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
body := handlers.PodTopOKBody{}
if err = response.Process(&body); err != nil {
return nil, err
}
// handlers.PodTopOKBody{} returns a slice of slices where each cell in the top table is an item.
// In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic
// usage of the tabwriter.
topOutput := []string{strings.Join(body.Titles, "\t")}
for _, out := range body.Processes {
topOutput = append(topOutput, strings.Join(out, "\t"))
}
return topOutput, err
}
// Unpause unpauses all paused containers in a Pod.
func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*entitiesTypes.PodUnpauseReport, error) {
if options == nil {
options = new(UnpauseOptions)
}
_ = options
var report entitiesTypes.PodUnpauseReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()
return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Stats display resource-usage statistics of one or more pods.
func Stats(ctx context.Context, namesOrIDs []string, options *StatsOptions) ([]*entitiesTypes.PodStatsReport, error) {
if options == nil {
options = new(StatsOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params, err := options.ToParams()
if err != nil {
return nil, err
}
for _, i := range namesOrIDs {
params.Add("namesOrIDs", i)
}
var reports []*entitiesTypes.PodStatsReport
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/stats", params, nil)
if err != nil {
return nil, err
}
defer response.Body.Close()
return reports, response.Process(&reports)
}

View file

@ -0,0 +1,92 @@
package pods
// CreateOptions are optional options for creating pods
//
//go:generate go run ../generator/generator.go CreateOptions
type CreateOptions struct {
}
// InspectOptions are optional options for inspecting pods
//
//go:generate go run ../generator/generator.go InspectOptions
type InspectOptions struct {
}
// KillOptions are optional options for killing pods
//
//go:generate go run ../generator/generator.go KillOptions
type KillOptions struct {
Signal *string
}
// PauseOptions are optional options for pausing pods
//
//go:generate go run ../generator/generator.go PauseOptions
type PauseOptions struct {
}
// PruneOptions are optional options for pruning pods
//
//go:generate go run ../generator/generator.go PruneOptions
type PruneOptions struct {
}
// ListOptions are optional options for listing pods
//
//go:generate go run ../generator/generator.go ListOptions
type ListOptions struct {
Filters map[string][]string
}
// RestartOptions are optional options for restarting pods
//
//go:generate go run ../generator/generator.go RestartOptions
type RestartOptions struct {
}
// StartOptions are optional options for starting pods
//
//go:generate go run ../generator/generator.go StartOptions
type StartOptions struct {
}
// StopOptions are optional options for stopping pods
//
//go:generate go run ../generator/generator.go StopOptions
type StopOptions struct {
Timeout *int
}
// TopOptions are optional options for getting top on pods
//
//go:generate go run ../generator/generator.go TopOptions
type TopOptions struct {
Descriptors []string
}
// UnpauseOptions are optional options for unpausinging pods
//
//go:generate go run ../generator/generator.go UnpauseOptions
type UnpauseOptions struct {
}
// StatsOptions are optional options for getting stats of pods
//
//go:generate go run ../generator/generator.go StatsOptions
type StatsOptions struct {
All *bool
}
// RemoveOptions are optional options for removing pods
//
//go:generate go run ../generator/generator.go RemoveOptions
type RemoveOptions struct {
Force *bool
Timeout *uint
}
// ExistsOptions are optional options for checking if a pod exists
//
//go:generate go run ../generator/generator.go ExistsOptions
type ExistsOptions struct {
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *CreateOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *CreateOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *ExistsOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *ExistsOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *InspectOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *InspectOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *KillOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *KillOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithSignal set field Signal to given value
func (o *KillOptions) WithSignal(value string) *KillOptions {
o.Signal = &value
return o
}
// GetSignal returns value of field Signal
func (o *KillOptions) GetSignal() string {
if o.Signal == nil {
var z string
return z
}
return *o.Signal
}

View file

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *ListOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *ListOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithFilters set field Filters to given value
func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions {
o.Filters = value
return o
}
// GetFilters returns value of field Filters
func (o *ListOptions) GetFilters() map[string][]string {
if o.Filters == nil {
var z map[string][]string
return z
}
return o.Filters
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *PauseOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *PauseOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *PruneOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *PruneOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,48 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *RemoveOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *RemoveOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithForce set field Force to given value
func (o *RemoveOptions) WithForce(value bool) *RemoveOptions {
o.Force = &value
return o
}
// GetForce returns value of field Force
func (o *RemoveOptions) GetForce() bool {
if o.Force == nil {
var z bool
return z
}
return *o.Force
}
// WithTimeout set field Timeout to given value
func (o *RemoveOptions) WithTimeout(value uint) *RemoveOptions {
o.Timeout = &value
return o
}
// GetTimeout returns value of field Timeout
func (o *RemoveOptions) GetTimeout() uint {
if o.Timeout == nil {
var z uint
return z
}
return *o.Timeout
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *RestartOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *RestartOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *StartOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *StartOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

View file

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *StatsOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *StatsOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithAll set field All to given value
func (o *StatsOptions) WithAll(value bool) *StatsOptions {
o.All = &value
return o
}
// GetAll returns value of field All
func (o *StatsOptions) GetAll() bool {
if o.All == nil {
var z bool
return z
}
return *o.All
}

View file

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *StopOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *StopOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithTimeout set field Timeout to given value
func (o *StopOptions) WithTimeout(value int) *StopOptions {
o.Timeout = &value
return o
}
// GetTimeout returns value of field Timeout
func (o *StopOptions) GetTimeout() int {
if o.Timeout == nil {
var z int
return z
}
return *o.Timeout
}

View file

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *TopOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *TopOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithDescriptors set field Descriptors to given value
func (o *TopOptions) WithDescriptors(value []string) *TopOptions {
o.Descriptors = value
return o
}
// GetDescriptors returns value of field Descriptors
func (o *TopOptions) GetDescriptors() []string {
if o.Descriptors == nil {
var z []string
return z
}
return o.Descriptors
}

View file

@ -0,0 +1,18 @@
// Code generated by go generate; DO NOT EDIT.
package pods
import (
"net/url"
"github.com/containers/podman/v5/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *UnpauseOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *UnpauseOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}

1
vendor/modules.txt vendored
View file

@ -165,6 +165,7 @@ github.com/containers/podman/v5/pkg/bindings/containers
github.com/containers/podman/v5/pkg/bindings/images
github.com/containers/podman/v5/pkg/bindings/internal/util
github.com/containers/podman/v5/pkg/bindings/network
github.com/containers/podman/v5/pkg/bindings/pods
github.com/containers/podman/v5/pkg/bindings/system
github.com/containers/podman/v5/pkg/bindings/volumes
github.com/containers/podman/v5/pkg/checkpoint/crutils