mirror of
https://github.com/containers/podman.git
synced 2025-05-20 08:36:23 +08:00

Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> add a test, improve logic of command filter Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> improve a test Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> improve test, update a man page Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> improve man page, runtime functions Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> move ExternalContainerFilter type to entities package Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> add external filters Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> add tests for external containers Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> add test for ps external id, ancestor Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> add tests for ps external filters of since, before Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> fix linter warnings, add completion for the name filter Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> resolve conflicts Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> check command length, filter containers liist by external key Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com> re-write test to remove buildah usage Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>
407 lines
12 KiB
Go
407 lines
12 KiB
Go
//go:build !remote
|
|
|
|
package ps
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
libnetworkTypes "github.com/containers/common/libnetwork/types"
|
|
"github.com/containers/podman/v5/libpod"
|
|
"github.com/containers/podman/v5/libpod/define"
|
|
"github.com/containers/podman/v5/pkg/domain/entities"
|
|
"github.com/containers/podman/v5/pkg/domain/filters"
|
|
psdefine "github.com/containers/podman/v5/pkg/ps/define"
|
|
"github.com/containers/storage"
|
|
"github.com/containers/storage/types"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ExternalContainerFilter is a function to determine whether a container list is included
|
|
// in command output. Container lists to be outputted are tested using the function.
|
|
// A true return will include the container list, a false return will exclude it.
|
|
type ExternalContainerFilter func(*entities.ListContainer) bool
|
|
|
|
func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOptions) ([]entities.ListContainer, error) {
|
|
var (
|
|
pss = []entities.ListContainer{}
|
|
)
|
|
filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters))
|
|
filterExtFuncs := make([]entities.ExternalContainerFilter, 0, len(options.Filters))
|
|
all := options.All || options.Last > 0
|
|
if len(options.Filters) > 0 {
|
|
for k, v := range options.Filters {
|
|
generatedFunc, err := filters.GenerateContainerFilterFuncs(k, v, runtime)
|
|
if err != nil && !options.External {
|
|
return nil, err
|
|
}
|
|
filterFuncs = append(filterFuncs, generatedFunc)
|
|
|
|
if options.External {
|
|
generatedExtFunc, err := filters.GenerateExternalContainerFilterFuncs(k, v, runtime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
filterExtFuncs = append(filterExtFuncs, generatedExtFunc)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Docker thinks that if status is given as an input, then we should override
|
|
// the all setting and always deal with all containers.
|
|
if len(options.Filters["status"]) > 0 {
|
|
all = true
|
|
}
|
|
if !all {
|
|
runningOnly, err := filters.GenerateContainerFilterFuncs("status", []string{define.ContainerStateRunning.String()}, runtime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
filterFuncs = append(filterFuncs, runningOnly)
|
|
}
|
|
|
|
// Load the containers with their states populated. This speeds things
|
|
// up considerably as we use a signel DB connection to load the
|
|
// containers' states instead of one per container.
|
|
//
|
|
// This may return slightly outdated states but that's acceptable for
|
|
// listing containers; any state is outdated the point a container lock
|
|
// gets released.
|
|
cons, err := runtime.GetContainers(true, filterFuncs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if options.Last > 0 {
|
|
// Sort the libpod containers
|
|
sort.Sort(SortCreateTime{SortContainers: cons})
|
|
// we should perform the lopping before we start getting
|
|
// the expensive information on containers
|
|
if options.Last < len(cons) {
|
|
cons = cons[:options.Last]
|
|
}
|
|
}
|
|
for _, con := range cons {
|
|
listCon, err := ListContainerBatch(runtime, con, options)
|
|
switch {
|
|
// ignore both no ctr and no such pod errors as it means the ctr is gone now
|
|
case errors.Is(err, define.ErrNoSuchCtr), errors.Is(err, define.ErrNoSuchPod):
|
|
continue
|
|
case err != nil:
|
|
return nil, err
|
|
default:
|
|
pss = append(pss, listCon)
|
|
}
|
|
}
|
|
|
|
if options.External {
|
|
listCon, err := GetExternalContainerLists(runtime, filterExtFuncs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pss = append(pss, listCon...)
|
|
}
|
|
|
|
// Sort the containers we got
|
|
sort.Sort(SortPSCreateTime{SortPSContainers: pss})
|
|
|
|
if options.Last > 0 {
|
|
// only return the "last" containers caller requested
|
|
if options.Last < len(pss) {
|
|
pss = pss[:options.Last]
|
|
}
|
|
}
|
|
return pss, nil
|
|
}
|
|
|
|
// GetExternalContainerLists returns list of external containers for e.g. created by buildah
|
|
func GetExternalContainerLists(runtime *libpod.Runtime, filterExtFuncs ...entities.ExternalContainerFilter) ([]entities.ListContainer, error) {
|
|
var (
|
|
pss = []*entities.ListContainer{}
|
|
)
|
|
|
|
externCons, err := runtime.StorageContainers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, con := range externCons {
|
|
listCon, err := ListStorageContainer(runtime, con)
|
|
switch {
|
|
case errors.Is(err, types.ErrLoadError):
|
|
continue
|
|
// Container could have been removed since listing
|
|
case errors.Is(err, types.ErrContainerUnknown):
|
|
continue
|
|
case err != nil:
|
|
return nil, err
|
|
default:
|
|
pss = append(pss, &listCon)
|
|
}
|
|
}
|
|
|
|
filteredPss := applyExternalContainersFilters(pss, filterExtFuncs...)
|
|
|
|
return filteredPss, nil
|
|
}
|
|
|
|
// Apply container filters on bunch of external container lists
|
|
func applyExternalContainersFilters(containersList []*entities.ListContainer, filters ...entities.ExternalContainerFilter) []entities.ListContainer {
|
|
ctrsFiltered := make([]entities.ListContainer, 0, len(containersList))
|
|
|
|
for _, ctr := range containersList {
|
|
include := true
|
|
for _, filter := range filters {
|
|
include = include && filter(ctr)
|
|
}
|
|
|
|
if include {
|
|
ctrsFiltered = append(ctrsFiltered, *ctr)
|
|
}
|
|
}
|
|
|
|
return ctrsFiltered
|
|
}
|
|
|
|
// ListContainerBatch is used in ps to reduce performance hits by "batching"
|
|
// locks.
|
|
func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities.ContainerListOptions) (entities.ListContainer, error) {
|
|
var (
|
|
conConfig *libpod.ContainerConfig
|
|
conState define.ContainerStatus
|
|
err error
|
|
exitCode int32
|
|
exited bool
|
|
pid int
|
|
size *psdefine.ContainerSize
|
|
startedTime time.Time
|
|
exitedTime time.Time
|
|
cgroup, ipc, mnt, net, pidns, user, uts string
|
|
portMappings []libnetworkTypes.PortMapping
|
|
networks []string
|
|
healthStatus string
|
|
restartCount uint
|
|
podName string
|
|
)
|
|
|
|
batchErr := ctr.Batch(func(c *libpod.Container) error {
|
|
if opts.Sync {
|
|
if err := c.Sync(); err != nil {
|
|
return fmt.Errorf("unable to update container state from OCI runtime: %w", err)
|
|
}
|
|
}
|
|
|
|
conConfig = c.ConfigNoCopy()
|
|
conState, err = c.State()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to obtain container state: %w", err)
|
|
}
|
|
|
|
exitCode, exited, err = c.ExitCode()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to obtain container exit code: %w", err)
|
|
}
|
|
startedTime, err = c.StartedTime()
|
|
if err != nil {
|
|
logrus.Errorf("Getting started time for %q: %v", c.ID(), err)
|
|
}
|
|
exitedTime, err = c.FinishedTime()
|
|
if err != nil {
|
|
logrus.Errorf("Getting exited time for %q: %v", c.ID(), err)
|
|
}
|
|
|
|
pid, err = c.PID()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to obtain container pid: %w", err)
|
|
}
|
|
|
|
portMappings, err = c.PortMappings()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
networks, err = c.Networks()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
healthStatus, err = c.HealthCheckStatus()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
restartCount, err = c.RestartCount()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Namespace {
|
|
ctrPID := strconv.Itoa(pid)
|
|
cgroup, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup"))
|
|
ipc, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc"))
|
|
mnt, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt"))
|
|
net, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net"))
|
|
pidns, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid"))
|
|
user, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user"))
|
|
uts, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts"))
|
|
}
|
|
if opts.Size {
|
|
size = new(psdefine.ContainerSize)
|
|
|
|
rootFsSize, err := c.RootFsSize()
|
|
if err != nil {
|
|
logrus.Errorf("Getting root fs size for %q: %v", c.ID(), err)
|
|
}
|
|
|
|
rwSize, err := c.RWSize()
|
|
if err != nil {
|
|
logrus.Errorf("Getting rw size for %q: %v", c.ID(), err)
|
|
}
|
|
|
|
size.RootFsSize = rootFsSize
|
|
size.RwSize = rwSize
|
|
}
|
|
|
|
if opts.Pod && len(conConfig.Pod) > 0 {
|
|
podName, err = rt.GetPodName(conConfig.Pod)
|
|
if err != nil {
|
|
return fmt.Errorf("could not find container %s pod (id %s) in state: %w", conConfig.ID, conConfig.Pod, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if batchErr != nil {
|
|
return entities.ListContainer{}, batchErr
|
|
}
|
|
|
|
ps := entities.ListContainer{
|
|
AutoRemove: ctr.AutoRemove(),
|
|
CIDFile: conConfig.Spec.Annotations[define.InspectAnnotationCIDFile],
|
|
Command: conConfig.Command,
|
|
Created: conConfig.CreatedTime,
|
|
ExitCode: exitCode,
|
|
Exited: exited,
|
|
ExitedAt: exitedTime.Unix(),
|
|
ExposedPorts: conConfig.ExposedPorts,
|
|
ID: conConfig.ID,
|
|
Image: conConfig.RootfsImageName,
|
|
ImageID: conConfig.RootfsImageID,
|
|
IsInfra: conConfig.IsInfra,
|
|
Labels: conConfig.Labels,
|
|
Mounts: ctr.UserVolumes(),
|
|
Names: []string{conConfig.Name},
|
|
Networks: networks,
|
|
Pid: pid,
|
|
Pod: conConfig.Pod,
|
|
PodName: podName,
|
|
Ports: portMappings,
|
|
Restarts: restartCount,
|
|
Size: size,
|
|
StartedAt: startedTime.Unix(),
|
|
State: conState.String(),
|
|
Status: healthStatus,
|
|
}
|
|
|
|
if opts.Namespace {
|
|
ps.Namespaces = entities.ListContainerNamespaces{
|
|
Cgroup: cgroup,
|
|
IPC: ipc,
|
|
MNT: mnt,
|
|
NET: net,
|
|
PIDNS: pidns,
|
|
User: user,
|
|
UTS: uts,
|
|
}
|
|
}
|
|
|
|
return ps, nil
|
|
}
|
|
|
|
func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container) (entities.ListContainer, error) {
|
|
name := "unknown"
|
|
if len(ctr.Names) > 0 {
|
|
name = ctr.Names[0]
|
|
}
|
|
|
|
ps := entities.ListContainer{
|
|
ID: ctr.ID,
|
|
Created: ctr.Created,
|
|
ImageID: ctr.ImageID,
|
|
State: "storage",
|
|
Names: []string{name},
|
|
}
|
|
|
|
buildahCtr, err := rt.IsBuildahContainer(ctr.ID)
|
|
if err != nil {
|
|
return ps, fmt.Errorf("determining buildah container for container %s: %w", ctr.ID, err)
|
|
}
|
|
|
|
if buildahCtr {
|
|
ps.Command = []string{"buildah"}
|
|
} else {
|
|
ps.Command = []string{"storage"}
|
|
}
|
|
|
|
imageName := ""
|
|
if ctr.ImageID != "" {
|
|
image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, nil)
|
|
if err != nil {
|
|
return ps, err
|
|
}
|
|
if len(image.NamesHistory()) > 0 {
|
|
imageName = image.NamesHistory()[0]
|
|
}
|
|
} else if buildahCtr {
|
|
imageName = "scratch"
|
|
}
|
|
|
|
ps.Image = imageName
|
|
return ps, nil
|
|
}
|
|
|
|
func getNamespaceInfo(path string) (string, error) {
|
|
val, err := os.Readlink(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("getting info from %q: %w", path, err)
|
|
}
|
|
return getStrFromSquareBrackets(val), nil
|
|
}
|
|
|
|
// getStrFromSquareBrackets gets the string inside [] from a string.
|
|
func getStrFromSquareBrackets(cmd string) string {
|
|
reg := regexp.MustCompile(`.*\[|\].*`)
|
|
arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",")
|
|
return strings.Join(arr, ",")
|
|
}
|
|
|
|
// SortContainers helps us set-up ability to sort by createTime
|
|
type SortContainers []*libpod.Container
|
|
|
|
func (a SortContainers) Len() int { return len(a) }
|
|
func (a SortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type SortCreateTime struct{ SortContainers }
|
|
|
|
func (a SortCreateTime) Less(i, j int) bool {
|
|
return a.SortContainers[i].CreatedTime().After(a.SortContainers[j].CreatedTime())
|
|
}
|
|
|
|
// SortPSContainers helps us set-up ability to sort by createTime
|
|
type SortPSContainers []entities.ListContainer
|
|
|
|
func (a SortPSContainers) Len() int { return len(a) }
|
|
func (a SortPSContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type SortPSCreateTime struct{ SortPSContainers }
|
|
|
|
func (a SortPSCreateTime) Less(i, j int) bool {
|
|
return a.SortPSContainers[i].Created.Before(a.SortPSContainers[j].Created)
|
|
}
|