mirror of
https://github.com/containers/podman
synced 2024-10-22 02:03:38 +00:00
342ce4db50
Wire up kpod ps with the new libpod container backend. Signed-off-by: baude <bbaude@redhat.com> Closes: #67 Approved by: rhatdan
796 lines
23 KiB
Go
796 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/go-units"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"k8s.io/apimachinery/pkg/fields"
|
|
|
|
"fmt"
|
|
"github.com/pkg/errors"
|
|
"github.com/projectatomic/libpod/cmd/kpod/formats"
|
|
"github.com/projectatomic/libpod/libkpod"
|
|
"github.com/projectatomic/libpod/libpod"
|
|
"github.com/projectatomic/libpod/oci"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
type psOptions struct {
|
|
all bool
|
|
filter string
|
|
format string
|
|
last int
|
|
latest bool
|
|
noTrunc bool
|
|
quiet bool
|
|
size bool
|
|
label string
|
|
namespace bool
|
|
}
|
|
|
|
type psTemplateParams struct {
|
|
ID string
|
|
Image string
|
|
Command string
|
|
CreatedAt string
|
|
RunningFor string
|
|
Status string
|
|
Ports string
|
|
Size string
|
|
Names string
|
|
Labels string
|
|
Mounts string
|
|
PID int
|
|
Cgroup string
|
|
IPC string
|
|
MNT string
|
|
NET string
|
|
PIDNS string
|
|
User string
|
|
UTS string
|
|
}
|
|
|
|
// psJSONParams is only used when the JSON format is specified,
|
|
// and is better for data processing from JSON.
|
|
// psJSONParams will be populated by data from libkpod.ContainerData,
|
|
// the members of the struct are the sama data types as their sources.
|
|
type psJSONParams struct {
|
|
ID string `json:"id"`
|
|
Image string `json:"image"`
|
|
ImageID string `json:"image_id"`
|
|
Command string `json:"command"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
RunningFor time.Duration `json:"runningFor"`
|
|
Status string `json:"status"`
|
|
Ports map[string]struct{} `json:"ports"`
|
|
Size uint `json:"size"`
|
|
Names string `json:"names"`
|
|
Labels fields.Set `json:"labels"`
|
|
Mounts []specs.Mount `json:"mounts"`
|
|
ContainerRunning bool `json:"ctrRunning"`
|
|
Namespaces *namespace `json:"namespace,omitempty"`
|
|
}
|
|
|
|
type namespace struct {
|
|
PID string `json:"pid,omitempty"`
|
|
Cgroup string `json:"cgroup,omitempty"`
|
|
IPC string `json:"ipc,omitempty"`
|
|
MNT string `json:"mnt,omitempty"`
|
|
NET string `json:"net,omitempty"`
|
|
PIDNS string `json:"pidns,omitempty"`
|
|
User string `json:"user,omitempty"`
|
|
UTS string `json:"uts,omitempty"`
|
|
}
|
|
|
|
var (
|
|
psFlags = []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "all, a",
|
|
Usage: "Show all the containers, default is only running containers",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "filter, f",
|
|
Usage: "Filter output based on conditions given",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "format",
|
|
Usage: "Pretty-print containers to JSON or using a Go template",
|
|
},
|
|
cli.IntFlag{
|
|
Name: "last, n",
|
|
Usage: "Print the n last created containers (all states)",
|
|
Value: -1,
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "latest, l",
|
|
Usage: "Show the latest container created (all states)",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "no-trunc",
|
|
Usage: "Display the extended information",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "quiet, q",
|
|
Usage: "Print the numeric IDs of the containers only",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "size, s",
|
|
Usage: "Display the total file sizes",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "namespace, ns",
|
|
Usage: "Display namespace information",
|
|
},
|
|
}
|
|
psDescription = "Prints out information about the containers"
|
|
psCommand = cli.Command{
|
|
Name: "ps",
|
|
Usage: "List containers",
|
|
Description: psDescription,
|
|
Flags: psFlags,
|
|
Action: psCmd,
|
|
ArgsUsage: "",
|
|
}
|
|
)
|
|
|
|
func psCmd(c *cli.Context) error {
|
|
if err := validateFlags(c, psFlags); err != nil {
|
|
return err
|
|
}
|
|
|
|
// latest, and last are mutually exclusive.
|
|
if c.Int("last") >= 0 && c.Bool("latest") {
|
|
return errors.Errorf("last and latest are mutually exclusive")
|
|
}
|
|
|
|
runtime, err := getRuntime(c)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error creating libpod runtime")
|
|
}
|
|
|
|
defer runtime.Shutdown(false)
|
|
|
|
if len(c.Args()) > 0 {
|
|
return errors.Errorf("too many arguments, ps takes no arguments")
|
|
}
|
|
|
|
format := genPsFormat(c.Bool("quiet"), c.Bool("size"), c.Bool("namespace"))
|
|
if c.IsSet("format") {
|
|
format = c.String("format")
|
|
}
|
|
|
|
opts := psOptions{
|
|
all: c.Bool("all"),
|
|
filter: c.String("filter"),
|
|
format: format,
|
|
last: c.Int("last"),
|
|
latest: c.Bool("latest"),
|
|
noTrunc: c.Bool("no-trunc"),
|
|
quiet: c.Bool("quiet"),
|
|
size: c.Bool("size"),
|
|
namespace: c.Bool("namespace"),
|
|
}
|
|
|
|
var filterFuncs []libpod.ContainerFilter
|
|
// When we are dealing with latest or last=n, we need to
|
|
// get all containers.
|
|
if !opts.all && !opts.latest && opts.last < 1 {
|
|
// only get running containers
|
|
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
|
|
state, _ := c.State()
|
|
return state == libpod.ContainerStateRunning
|
|
})
|
|
}
|
|
|
|
if opts.filter != "" {
|
|
filters := strings.Split(opts.filter, ",")
|
|
for _, f := range filters {
|
|
filterSplit := strings.Split(f, "=")
|
|
if len(filterSplit) < 2 {
|
|
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
|
|
}
|
|
generatedFunc, err := generateContainerFilterFuncs(filterSplit[0], filterSplit[1], runtime)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid filter")
|
|
}
|
|
filterFuncs = append(filterFuncs, generatedFunc)
|
|
}
|
|
}
|
|
|
|
containers, err := runtime.GetContainers(filterFuncs...)
|
|
var outputContainers []*libpod.Container
|
|
if opts.latest && len(containers) > 0 {
|
|
outputContainers = append(outputContainers, containers[0])
|
|
} else if opts.last > 0 && opts.last <= len(containers) {
|
|
outputContainers = append(outputContainers, containers[:opts.last]...)
|
|
} else {
|
|
outputContainers = containers
|
|
}
|
|
|
|
return generatePsOutput(outputContainers, opts)
|
|
}
|
|
|
|
func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(container *libpod.Container) bool, error) {
|
|
switch filter {
|
|
case "id":
|
|
return func(c *libpod.Container) bool {
|
|
return c.ID() == filterValue
|
|
}, nil
|
|
case "label":
|
|
return func(c *libpod.Container) bool {
|
|
for _, label := range c.Labels() {
|
|
if label == filterValue {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}, nil
|
|
case "name":
|
|
return func(c *libpod.Container) bool {
|
|
return c.Name() == filterValue
|
|
}, nil
|
|
case "exited":
|
|
exitCode, err := strconv.ParseInt(filterValue, 10, 32)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "exited code out of range %q", filterValue)
|
|
}
|
|
return func(c *libpod.Container) bool {
|
|
ec, err := c.ExitCode()
|
|
if ec == int32(exitCode) && err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}, nil
|
|
case "status":
|
|
if !libpod.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) {
|
|
return nil, errors.Errorf("%s is not a valid status", filterValue)
|
|
}
|
|
return func(c *libpod.Container) bool {
|
|
status, err := c.State()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return status.String() == filterValue
|
|
}, nil
|
|
case "ancestor":
|
|
// This needs to refine to match docker
|
|
// - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant.
|
|
return func(c *libpod.Container) bool {
|
|
containerConfig := c.Config()
|
|
if containerConfig.RootfsImageID == filterValue || containerConfig.RootfsImageName == filterValue {
|
|
return true
|
|
}
|
|
return false
|
|
}, nil
|
|
case "before":
|
|
ctr, err := runtime.LookupContainer(filterValue)
|
|
if err != nil {
|
|
return nil, errors.Errorf("unable to find container by name or id of %s", filterValue)
|
|
}
|
|
containerConfig := ctr.Config()
|
|
createTime := containerConfig.CreatedTime
|
|
return func(c *libpod.Container) bool {
|
|
cc := c.Config()
|
|
return createTime.After(cc.CreatedTime)
|
|
}, nil
|
|
case "since":
|
|
ctr, err := runtime.LookupContainer(filterValue)
|
|
if err != nil {
|
|
return nil, errors.Errorf("unable to find container by name or id of %s", filterValue)
|
|
}
|
|
containerConfig := ctr.Config()
|
|
createTime := containerConfig.CreatedTime
|
|
return func(c *libpod.Container) bool {
|
|
cc := c.Config()
|
|
return createTime.Before(cc.CreatedTime)
|
|
}, nil
|
|
case "volume":
|
|
//- volume=(<volume-name>|<mount-point-destination>)
|
|
return func(c *libpod.Container) bool {
|
|
containerConfig := c.Config()
|
|
//TODO We need to still lookup against volumes too
|
|
return containerConfig.MountLabel == filterValue
|
|
}, nil
|
|
}
|
|
return nil, errors.Errorf("%s is an invalid filter", filter)
|
|
}
|
|
|
|
// generate the template based on conditions given
|
|
func genPsFormat(quiet, size, namespace bool) (format string) {
|
|
if quiet {
|
|
return formats.IDString
|
|
}
|
|
if namespace {
|
|
format = "table {{.ID}}\t{{.Names}}\t{{.PID}}\t{{.Cgroup}}\t{{.IPC}}\t{{.MNT}}\t{{.NET}}\t{{.PIDNS}}\t{{.User}}\t{{.UTS}}\t"
|
|
return
|
|
}
|
|
format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t"
|
|
if size {
|
|
format += "{{.Size}}\t"
|
|
}
|
|
return
|
|
}
|
|
|
|
func psToGeneric(templParams []psTemplateParams, JSONParams []psJSONParams) (genericParams []interface{}) {
|
|
if len(templParams) > 0 {
|
|
for _, v := range templParams {
|
|
genericParams = append(genericParams, interface{}(v))
|
|
}
|
|
return
|
|
}
|
|
for _, v := range JSONParams {
|
|
genericParams = append(genericParams, interface{}(v))
|
|
}
|
|
return
|
|
}
|
|
|
|
// generate the accurate header based on template given
|
|
func (p *psTemplateParams) headerMap() map[string]string {
|
|
v := reflect.Indirect(reflect.ValueOf(p))
|
|
values := make(map[string]string)
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
key := v.Type().Field(i).Name
|
|
value := key
|
|
if value == "ID" {
|
|
value = "Container" + value
|
|
}
|
|
values[key] = strings.ToUpper(splitCamelCase(value))
|
|
}
|
|
return values
|
|
}
|
|
|
|
// getContainers gets the containers that match the flags given
|
|
func getContainers(containers []*libkpod.ContainerData, opts psOptions) []*libkpod.ContainerData {
|
|
var containersOutput []*libkpod.ContainerData
|
|
if opts.last >= 0 && opts.last < len(containers) {
|
|
for i := 0; i < opts.last; i++ {
|
|
containersOutput = append(containersOutput, containers[i])
|
|
}
|
|
return containersOutput
|
|
}
|
|
if opts.latest {
|
|
return []*libkpod.ContainerData{containers[0]}
|
|
}
|
|
if opts.all || opts.last >= len(containers) {
|
|
return containers
|
|
}
|
|
for _, ctr := range containers {
|
|
if ctr.State.Status == oci.ContainerStateRunning {
|
|
containersOutput = append(containersOutput, ctr)
|
|
}
|
|
}
|
|
return containersOutput
|
|
}
|
|
|
|
// getTemplateOutput returns the modified container information
|
|
func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemplateParams, error) {
|
|
var psOutput []psTemplateParams
|
|
var status string
|
|
for _, ctr := range containers {
|
|
ctrID := ctr.ID()
|
|
conConfig := ctr.Config()
|
|
conState, err := ctr.State()
|
|
if err != nil {
|
|
return psOutput, errors.Wrapf(err, "unable to obtain container state")
|
|
}
|
|
exitCode, err := ctr.ExitCode()
|
|
if err != nil {
|
|
return psOutput, errors.Wrapf(err, "unable to obtain container exit code")
|
|
}
|
|
pid, err := ctr.PID()
|
|
if err != nil {
|
|
return psOutput, errors.Wrapf(err, "unable to obtain container pid")
|
|
}
|
|
runningFor := units.HumanDuration(time.Since(conConfig.CreatedTime))
|
|
createdAt := runningFor + " ago"
|
|
imageName := conConfig.RootfsImageName
|
|
|
|
// TODO We currently dont have the ability to get many of
|
|
// these data items. Uncomment as progress is made
|
|
|
|
//command := getStrFromSquareBrackets(ctr.ImageCreatedBy)
|
|
command := strings.Join(ctr.Spec().Process.Args, " ")
|
|
//mounts := getMounts(ctr.Mounts, opts.noTrunc)
|
|
//ports := getPorts(ctr.Config.ExposedPorts)
|
|
//size := units.HumanSize(float64(ctr.SizeRootFs))
|
|
labels := formatLabels(ctr.Labels())
|
|
ns := getNamespaces(pid)
|
|
|
|
switch conState {
|
|
case libpod.ContainerStateStopped:
|
|
status = fmt.Sprintf("Exited (%d) %s ago", exitCode, runningFor)
|
|
case libpod.ContainerStateRunning:
|
|
status = "Up " + runningFor + " ago"
|
|
case libpod.ContainerStatePaused:
|
|
status = "Paused"
|
|
case libpod.ContainerStateCreated:
|
|
status = "Created"
|
|
default:
|
|
status = "Dead"
|
|
}
|
|
|
|
if !opts.noTrunc {
|
|
ctrID = ctr.ID()[:idTruncLength]
|
|
imageName = conConfig.RootfsImageName
|
|
}
|
|
|
|
// TODO We currently dont have the ability to get many of
|
|
// these data items. Uncomment as progress is made
|
|
|
|
params := psTemplateParams{
|
|
ID: ctrID,
|
|
Image: imageName,
|
|
Command: command,
|
|
CreatedAt: createdAt,
|
|
RunningFor: runningFor,
|
|
Status: status,
|
|
//Ports: ports,
|
|
//Size: size,
|
|
Names: ctr.Name(),
|
|
Labels: labels,
|
|
//Mounts: mounts,
|
|
PID: pid,
|
|
Cgroup: ns.Cgroup,
|
|
IPC: ns.IPC,
|
|
MNT: ns.MNT,
|
|
NET: ns.NET,
|
|
PIDNS: ns.PID,
|
|
User: ns.User,
|
|
UTS: ns.UTS,
|
|
}
|
|
psOutput = append(psOutput, params)
|
|
}
|
|
return psOutput, nil
|
|
}
|
|
|
|
func getNamespaces(pid int) *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"))
|
|
|
|
return &namespace{
|
|
PID: ctrPID,
|
|
Cgroup: cgroup,
|
|
IPC: ipc,
|
|
MNT: mnt,
|
|
NET: net,
|
|
PIDNS: pidns,
|
|
User: user,
|
|
UTS: uts,
|
|
}
|
|
}
|
|
|
|
func getNamespaceInfo(path string) (string, error) {
|
|
val, err := os.Readlink(path)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "error getting info from %q", path)
|
|
}
|
|
return getStrFromSquareBrackets(val), nil
|
|
}
|
|
|
|
// getJSONOutput returns the container info in its raw form
|
|
func getJSONOutput(containers []*libpod.Container, nSpace bool) ([]psJSONParams, error) {
|
|
var psOutput []psJSONParams
|
|
var ns *namespace
|
|
for _, ctr := range containers {
|
|
pid, err := ctr.PID()
|
|
if err != nil {
|
|
return psOutput, errors.Wrapf(err, "unable to obtain container pid")
|
|
}
|
|
if nSpace {
|
|
ns = getNamespaces(pid)
|
|
}
|
|
cc := ctr.Config()
|
|
conState, err := ctr.State()
|
|
if err != nil {
|
|
return psOutput, errors.Wrapf(err, "unable to obtain container state for JSON output")
|
|
}
|
|
params := psJSONParams{
|
|
// TODO When we have ability to obtain the commented out data, we need
|
|
// TODO to add it
|
|
ID: ctr.ID(),
|
|
Image: cc.RootfsImageName,
|
|
ImageID: cc.RootfsImageID,
|
|
//Command: getStrFromSquareBrackets(ctr.ImageCreatedBy),
|
|
Command: strings.Join(ctr.Spec().Process.Args, " "),
|
|
CreatedAt: cc.CreatedTime,
|
|
RunningFor: time.Since(cc.CreatedTime),
|
|
Status: conState.String(),
|
|
//Ports: cc.Spec.Linux.Resources.Network.
|
|
//Size: ctr.SizeRootFs,
|
|
Names: cc.Name,
|
|
Labels: cc.Labels,
|
|
Mounts: cc.Spec.Mounts,
|
|
ContainerRunning: conState.String() == oci.ContainerStateRunning,
|
|
Namespaces: ns,
|
|
}
|
|
psOutput = append(psOutput, params)
|
|
}
|
|
return psOutput, nil
|
|
}
|
|
|
|
func generatePsOutput(containers []*libpod.Container, opts psOptions) error {
|
|
if len(containers) == 0 && opts.format != formats.JSONString {
|
|
return nil
|
|
}
|
|
var out formats.Writer
|
|
|
|
switch opts.format {
|
|
case formats.JSONString:
|
|
psOutput, err := getJSONOutput(containers, opts.namespace)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to create JSON for output")
|
|
}
|
|
out = formats.JSONStructArray{Output: psToGeneric([]psTemplateParams{}, psOutput)}
|
|
default:
|
|
psOutput, err := getTemplateOutput(containers, opts)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to create output")
|
|
}
|
|
out = formats.StdoutTemplateArray{Output: psToGeneric(psOutput, []psJSONParams{}), Template: opts.format, Fields: psOutput[0].headerMap()}
|
|
}
|
|
|
|
return formats.Writer(out).Out()
|
|
}
|
|
|
|
// getStrFromSquareBrackets gets the string inside [] from a string
|
|
func getStrFromSquareBrackets(cmd string) string {
|
|
reg, err := regexp.Compile(".*\\[|\\].*")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",")
|
|
return strings.Join(arr, ",")
|
|
}
|
|
|
|
// getImageName shortens the image name
|
|
func getImageName(img string) string {
|
|
arr := strings.Split(img, "/")
|
|
if arr[0] == "docker.io" && arr[1] == "library" {
|
|
img = strings.Join(arr[2:], "/")
|
|
} else if arr[0] == "docker.io" {
|
|
img = strings.Join(arr[1:], "/")
|
|
}
|
|
return img
|
|
}
|
|
|
|
// getLabels converts the labels to a string of the form "key=value, key2=value2"
|
|
func formatLabels(labels map[string]string) string {
|
|
var arr []string
|
|
if len(labels) > 0 {
|
|
for key, val := range labels {
|
|
temp := key + "=" + val
|
|
arr = append(arr, temp)
|
|
}
|
|
return strings.Join(arr, ",")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// getMounts converts the volumes mounted to a string of the form "mount1, mount2"
|
|
// it truncates it if noTrunc is false
|
|
func getMounts(mounts []specs.Mount, noTrunc bool) string {
|
|
var arr []string
|
|
if len(mounts) == 0 {
|
|
return ""
|
|
}
|
|
for _, mount := range mounts {
|
|
if noTrunc {
|
|
arr = append(arr, mount.Source)
|
|
continue
|
|
}
|
|
tempArr := strings.SplitAfter(mount.Source, "/")
|
|
if len(tempArr) >= 3 {
|
|
arr = append(arr, strings.Join(tempArr[:3], ""))
|
|
} else {
|
|
arr = append(arr, mount.Source)
|
|
}
|
|
}
|
|
return strings.Join(arr, ",")
|
|
}
|
|
|
|
// getPorts converts the ports used to a string of the from "port1, port2"
|
|
func getPorts(ports map[string]struct{}) string {
|
|
var arr []string
|
|
if len(ports) == 0 {
|
|
return ""
|
|
}
|
|
for key := range ports {
|
|
arr = append(arr, key)
|
|
}
|
|
return strings.Join(arr, ",")
|
|
}
|
|
|
|
// FilterParamsPS contains the filter options for ps
|
|
type FilterParamsPS struct {
|
|
id string
|
|
label string
|
|
name string
|
|
exited int32
|
|
status string
|
|
ancestor string
|
|
before time.Time
|
|
since time.Time
|
|
volume string
|
|
}
|
|
|
|
// parseFilter takes a filter string and a list of containers and filters it
|
|
func parseFilter(filter string, containers []*oci.Container) (*FilterParamsPS, error) {
|
|
params := new(FilterParamsPS)
|
|
allFilters := strings.Split(filter, ",")
|
|
|
|
for _, param := range allFilters {
|
|
pair := strings.SplitN(param, "=", 2)
|
|
switch strings.TrimSpace(pair[0]) {
|
|
case "id":
|
|
params.id = pair[1]
|
|
case "label":
|
|
params.label = pair[1]
|
|
case "name":
|
|
params.name = pair[1]
|
|
case "exited":
|
|
exitedCode, err := strconv.ParseInt(pair[1], 10, 32)
|
|
if err != nil {
|
|
return nil, errors.Errorf("exited code out of range %q", pair[1])
|
|
}
|
|
params.exited = int32(exitedCode)
|
|
case "status":
|
|
params.status = pair[1]
|
|
case "ancestor":
|
|
params.ancestor = pair[1]
|
|
case "before":
|
|
if ctr, err := findContainer(containers, pair[1]); err == nil {
|
|
params.before = ctr.CreatedAt()
|
|
} else {
|
|
return nil, errors.Wrapf(err, "no such container %q", pair[1])
|
|
}
|
|
case "since":
|
|
if ctr, err := findContainer(containers, pair[1]); err == nil {
|
|
params.before = ctr.CreatedAt()
|
|
} else {
|
|
return nil, errors.Wrapf(err, "no such container %q", pair[1])
|
|
}
|
|
case "volume":
|
|
params.volume = pair[1]
|
|
default:
|
|
return nil, errors.Errorf("invalid filter %q", pair[0])
|
|
}
|
|
}
|
|
return params, nil
|
|
}
|
|
|
|
// findContainer finds a container with a specific name or id from a list of containers
|
|
func findContainer(containers []*oci.Container, ref string) (*oci.Container, error) {
|
|
for _, ctr := range containers {
|
|
if strings.HasPrefix(ctr.ID(), ref) || ctr.Name() == ref {
|
|
return ctr, nil
|
|
}
|
|
}
|
|
return nil, errors.Errorf("could not find container")
|
|
}
|
|
|
|
// matchesFilter checks if a container matches all the filter parameters
|
|
func matchesFilter(ctrData *libkpod.ContainerData, params *FilterParamsPS) bool {
|
|
if params == nil {
|
|
return true
|
|
}
|
|
if params.id != "" && !matchesID(ctrData, params.id) {
|
|
return false
|
|
}
|
|
if params.name != "" && !matchesName(ctrData, params.name) {
|
|
return false
|
|
}
|
|
if !params.before.IsZero() && !matchesBeforeContainer(ctrData, params.before) {
|
|
return false
|
|
}
|
|
if !params.since.IsZero() && !matchesSinceContainer(ctrData, params.since) {
|
|
return false
|
|
}
|
|
if params.exited > 0 && !matchesExited(ctrData, params.exited) {
|
|
return false
|
|
}
|
|
if params.status != "" && !matchesStatus(ctrData, params.status) {
|
|
return false
|
|
}
|
|
if params.ancestor != "" && !matchesAncestor(ctrData, params.ancestor) {
|
|
return false
|
|
}
|
|
if params.label != "" && !matchesLabel(ctrData, params.label) {
|
|
return false
|
|
}
|
|
if params.volume != "" && !matchesVolume(ctrData, params.volume) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetContainersMatchingFilter returns a slice of all the containers that match the provided filter parameters
|
|
func getContainersMatchingFilter(containers []*oci.Container, filter *FilterParamsPS, server *libkpod.ContainerServer) []*libkpod.ContainerData {
|
|
var filteredCtrs []*libkpod.ContainerData
|
|
for _, ctr := range containers {
|
|
ctrData, err := server.GetContainerData(ctr.ID(), true)
|
|
if err != nil {
|
|
logrus.Warn("unable to get container data for matched container")
|
|
}
|
|
if filter == nil || matchesFilter(ctrData, filter) {
|
|
filteredCtrs = append(filteredCtrs, ctrData)
|
|
}
|
|
}
|
|
return filteredCtrs
|
|
}
|
|
|
|
// matchesID returns true if the id's match
|
|
func matchesID(ctrData *libkpod.ContainerData, id string) bool {
|
|
return strings.HasPrefix(ctrData.ID, id)
|
|
}
|
|
|
|
// matchesBeforeContainer returns true if the container was created before the filter image
|
|
func matchesBeforeContainer(ctrData *libkpod.ContainerData, beforeTime time.Time) bool {
|
|
return ctrData.State.Created.Before(beforeTime)
|
|
}
|
|
|
|
// matchesSincecontainer returns true if the container was created since the filter image
|
|
func matchesSinceContainer(ctrData *libkpod.ContainerData, sinceTime time.Time) bool {
|
|
return ctrData.State.Created.After(sinceTime)
|
|
}
|
|
|
|
// matchesLabel returns true if the container label matches that of the filter label
|
|
func matchesLabel(ctrData *libkpod.ContainerData, label string) bool {
|
|
pair := strings.SplitN(label, "=", 2)
|
|
if val, ok := ctrData.Labels[pair[0]]; ok {
|
|
if len(pair) == 2 && val == pair[1] {
|
|
return true
|
|
}
|
|
if len(pair) == 1 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
// matchesName returns true if the names are identical
|
|
func matchesName(ctrData *libkpod.ContainerData, name string) bool {
|
|
return ctrData.Name == name
|
|
}
|
|
|
|
// matchesExited returns true if the exit codes are identical
|
|
func matchesExited(ctrData *libkpod.ContainerData, exited int32) bool {
|
|
return ctrData.State.ExitCode == exited
|
|
}
|
|
|
|
// matchesStatus returns true if the container status matches that of filter status
|
|
func matchesStatus(ctrData *libkpod.ContainerData, status string) bool {
|
|
return ctrData.State.Status == status
|
|
}
|
|
|
|
// matchesAncestor returns true if filter ancestor is in container image name
|
|
func matchesAncestor(ctrData *libkpod.ContainerData, ancestor string) bool {
|
|
return strings.Contains(ctrData.FromImage, ancestor)
|
|
}
|
|
|
|
// matchesVolue returns true if the volume mounted or path to volue of the container matches that of filter volume
|
|
func matchesVolume(ctrData *libkpod.ContainerData, volume string) bool {
|
|
for _, vol := range ctrData.Mounts {
|
|
if strings.Contains(vol.Source, volume) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|