automation-tests/cmd/podman/pod_ps.go

566 lines
15 KiB
Go

package main
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/adapter"
"github.com/containers/libpod/pkg/util"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
const (
STOPPED = "Stopped" //nolint
RUNNING = "Running"
PAUSED = "Paused"
EXITED = "Exited"
ERROR = "Error"
CREATED = "Created"
NUM_CTR_INFO = 10
)
type PodFilter func(*adapter.Pod) bool
var (
bc_opts shared.PsOptions
)
type podPsCtrInfo struct {
Name string `json:"name,omitempty"`
Id string `json:"id,omitempty"`
Status string `json:"status,omitempty"`
}
type podPsOptions struct {
NoTrunc bool
Format string
Sort string
Quiet bool
NumberOfContainers bool
Cgroup bool
NamesOfContainers bool
IdsOfContainers bool
StatusOfContainers bool
}
type podPsTemplateParams struct {
Created string
ID string
Name string
NumberOfContainers int
Status string
Cgroup string
ContainerInfo string
InfraID string
Namespaces string
}
// podPsJSONParams is used as a base structure for the psParams
// If template output is requested, podPsJSONParams will be converted to
// podPsTemplateParams.
// podPsJSONParams will be populated by data from libpod.Container,
// the members of the struct are the sama data types as their sources.
type podPsJSONParams struct {
CreatedAt time.Time `json:"createdAt"`
ID string `json:"id"`
Name string `json:"name"`
NumberOfContainers int `json:"numberOfContainers"`
Status string `json:"status"`
CtrsInfo []podPsCtrInfo `json:"containerInfo,omitempty"`
Cgroup string `json:"cgroup,omitempty"`
InfraID string `json:"infraContainerId,omitempty"`
Namespaces []string `json:"namespaces,omitempty"`
}
// Type declaration and functions for sorting the pod PS output
type podPsSorted []podPsJSONParams
func (a podPsSorted) Len() int { return len(a) }
func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type podPsSortedCreated struct{ podPsSorted }
func (a podPsSortedCreated) Less(i, j int) bool {
return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt)
}
type podPsSortedId struct{ podPsSorted }
func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID }
type podPsSortedNumber struct{ podPsSorted }
func (a podPsSortedNumber) Less(i, j int) bool {
return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo)
}
type podPsSortedName struct{ podPsSorted }
func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name }
type podPsSortedStatus struct{ podPsSorted }
func (a podPsSortedStatus) Less(i, j int) bool {
return a.podPsSorted[i].Status < a.podPsSorted[j].Status
}
var (
podPsCommand cliconfig.PodPsValues
podPsDescription = "List all pods on system including their names, ids and current state."
_podPsCommand = &cobra.Command{
Use: "ps",
Aliases: []string{"ls", "list"},
Args: noSubArgs,
Short: "List pods",
Long: podPsDescription,
RunE: func(cmd *cobra.Command, args []string) error {
podPsCommand.InputArgs = args
podPsCommand.GlobalFlags = MainGlobalOpts
podPsCommand.Remote = remoteclient
return podPsCmd(&podPsCommand)
},
}
)
func init() {
podPsCommand.Command = _podPsCommand
podPsCommand.SetHelpTemplate(HelpTemplate())
podPsCommand.SetUsageTemplate(UsageTemplate())
flags := podPsCommand.Flags()
flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names")
flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated")
flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status")
flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given")
flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template")
flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of")
flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod")
flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod")
flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs")
flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only")
flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number")
markFlagHiddenForRemoteClient("latest", flags)
}
func podPsCmd(c *cliconfig.PodPsValues) error {
if err := podPsCheckFlagsPassed(c); err != nil {
return errors.Wrapf(err, "error with flags passed")
}
runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.DeferredShutdown(false)
opts := podPsOptions{
NoTrunc: c.NoTrunc,
Quiet: c.Quiet,
Sort: c.Sort,
IdsOfContainers: c.CtrIDs,
NamesOfContainers: c.CtrNames,
StatusOfContainers: c.CtrStatus,
}
opts.Format = genPodPsFormat(c)
var filterFuncs []PodFilter
if c.Filter != "" {
filters := strings.Split(c.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 := generatePodFilterFuncs(filterSplit[0], filterSplit[1])
if err != nil {
return errors.Wrapf(err, "invalid filter")
}
filterFuncs = append(filterFuncs, generatedFunc)
}
}
var pods []*adapter.Pod
if c.Latest {
pod, err := runtime.GetLatestPod()
if err != nil {
return err
}
pods = append(pods, pod)
} else {
pods, err = runtime.GetAllPods()
if err != nil {
return err
}
}
podsFiltered := make([]*adapter.Pod, 0, len(pods))
for _, pod := range pods {
include := true
for _, filter := range filterFuncs {
include = include && filter(pod)
}
if include {
podsFiltered = append(podsFiltered, pod)
}
}
return generatePodPsOutput(podsFiltered, opts)
}
// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together
func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error {
// quiet, and format with Go template are mutually exclusive
flags := 0
if c.Quiet {
flags++
}
if c.Flag("format").Changed && c.Format != formats.JSONString {
flags++
}
if flags > 1 {
return errors.Errorf("quiet and format with Go template are mutually exclusive")
}
return nil
}
func generatePodFilterFuncs(filter, filterValue string) (func(pod *adapter.Pod) bool, error) {
switch filter {
case "ctr-ids":
return func(p *adapter.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
return util.StringInSlice(filterValue, ctrIds)
}, nil
case "ctr-names":
return func(p *adapter.Pod) bool {
ctrs, err := p.AllContainers()
if err != nil {
return false
}
for _, ctr := range ctrs {
if filterValue == ctr.Name() {
return true
}
}
return false
}, nil
case "ctr-number":
return func(p *adapter.Pod) bool {
ctrIds, err := p.AllContainersByID()
if err != nil {
return false
}
fVint, err2 := strconv.Atoi(filterValue)
if err2 != nil {
return false
}
return len(ctrIds) == fVint
}, nil
case "ctr-status":
if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) {
return nil, errors.Errorf("%s is not a valid status", filterValue)
}
return func(p *adapter.Pod) bool {
ctr_statuses, err := p.Status()
if err != nil {
return false
}
for _, ctr_status := range ctr_statuses {
state := ctr_status.String()
if ctr_status == define.ContainerStateConfigured {
state = "created"
}
if state == filterValue {
return true
}
}
return false
}, nil
case "id":
return func(p *adapter.Pod) bool {
return strings.Contains(p.ID(), filterValue)
}, nil
case "name":
return func(p *adapter.Pod) bool {
return strings.Contains(p.Name(), filterValue)
}, nil
case "status":
if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
return nil, errors.Errorf("%s is not a valid pod status", filterValue)
}
return func(p *adapter.Pod) bool {
status, err := p.GetPodStatus()
if err != nil {
return false
}
if strings.ToLower(status) == filterValue {
return true
}
return false
}, nil
}
return nil, errors.Errorf("%s is an invalid filter", filter)
}
// generate the template based on conditions given
func genPodPsFormat(c *cliconfig.PodPsValues) string {
format := ""
if c.Format != "" {
// "\t" from the command line is not being recognized as a tab
// replacing the string "\t" to a tab character if the user passes in "\t"
format = strings.Replace(c.Format, `\t`, "\t", -1)
} else if c.Quiet {
format = formats.IDString
} else {
format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}"
if c.Bool("namespace") {
format += "\t{{.Cgroup}}\t{{.Namespaces}}"
}
if c.CtrNames || c.CtrIDs || c.CtrStatus {
format += "\t{{.ContainerInfo}}"
} else {
format += "\t{{.NumberOfContainers}}"
}
format += "\t{{.InfraID}}"
}
return format
}
func podPsToGeneric(templParams []podPsTemplateParams, JSONParams []podPsJSONParams) (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 *podPsTemplateParams) podHeaderMap() 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 = "Pod" + value
}
if value == "NumberOfContainers" {
value = "#OfContainers"
}
values[key] = strings.ToUpper(splitCamelCase(value))
}
return values
}
func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) {
switch sortBy {
case "created":
sort.Sort(podPsSortedCreated{psOutput})
case "id":
sort.Sort(podPsSortedId{psOutput})
case "name":
sort.Sort(podPsSortedName{psOutput})
case "number":
sort.Sort(podPsSortedNumber{psOutput})
case "status":
sort.Sort(podPsSortedStatus{psOutput})
default:
return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number")
}
return psOutput, nil
}
// getPodTemplateOutput returns the modified container information
func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) {
var (
psOutput []podPsTemplateParams
)
for _, psParam := range psParams {
podID := psParam.ID
infraID := psParam.InfraID
var ctrStr string
truncated := ""
if !opts.NoTrunc {
podID = shortID(podID)
if len(psParam.CtrsInfo) > NUM_CTR_INFO {
psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO]
truncated = "..."
}
infraID = shortID(infraID)
}
for _, ctrInfo := range psParam.CtrsInfo {
infoSlice := make([]string, 0)
if opts.IdsOfContainers {
if opts.NoTrunc {
infoSlice = append(infoSlice, ctrInfo.Id)
} else {
infoSlice = append(infoSlice, shortID(ctrInfo.Id))
}
}
if opts.NamesOfContainers {
infoSlice = append(infoSlice, ctrInfo.Name)
}
if opts.StatusOfContainers {
infoSlice = append(infoSlice, ctrInfo.Status)
}
if len(infoSlice) != 0 {
ctrStr += fmt.Sprintf("[%s] ", strings.Join(infoSlice, ","))
}
}
ctrStr += truncated
params := podPsTemplateParams{
Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago",
ID: podID,
Name: psParam.Name,
Status: psParam.Status,
NumberOfContainers: psParam.NumberOfContainers,
Cgroup: psParam.Cgroup,
ContainerInfo: ctrStr,
InfraID: infraID,
Namespaces: strings.Join(psParam.Namespaces, ","),
}
psOutput = append(psOutput, params)
}
return psOutput, nil
}
func getNamespaces(pod *adapter.Pod) []string {
var shared []string
if pod.SharesPID() {
shared = append(shared, "pid")
}
if pod.SharesNet() {
shared = append(shared, "net")
}
if pod.SharesMount() {
shared = append(shared, "mnt")
}
if pod.SharesIPC() {
shared = append(shared, "ipc")
}
if pod.SharesUser() {
shared = append(shared, "user")
}
if pod.SharesCgroup() {
shared = append(shared, "cgroup")
}
if pod.SharesUTS() {
shared = append(shared, "uts")
}
return shared
}
// getAndSortPodJSONOutput returns the container info in its raw, sorted form
func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) {
var (
psOutput []podPsJSONParams
)
for _, pod := range pods {
ctrs, err := pod.AllContainers()
ctrsInfo := make([]podPsCtrInfo, 0)
if err != nil {
return nil, err
}
ctrNum := len(ctrs)
status, err := pod.GetPodStatus()
if err != nil {
return nil, err
}
infraID, err := pod.InfraContainerID()
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts)
if err != nil {
return nil, err
}
var status string
switch batchInfo.ConState {
case define.ContainerStateExited:
fallthrough
case define.ContainerStateStopped:
status = EXITED
case define.ContainerStateRunning:
status = RUNNING
case define.ContainerStatePaused:
status = PAUSED
case define.ContainerStateCreated, define.ContainerStateConfigured:
status = CREATED
default:
status = ERROR
}
ctrsInfo = append(ctrsInfo, podPsCtrInfo{
Name: batchInfo.ConConfig.Name,
Id: ctr.ID(),
Status: status,
})
}
params := podPsJSONParams{
CreatedAt: pod.CreatedTime(),
ID: pod.ID(),
Name: pod.Name(),
Status: status,
Cgroup: pod.CgroupParent(),
NumberOfContainers: ctrNum,
CtrsInfo: ctrsInfo,
Namespaces: getNamespaces(pod),
InfraID: infraID,
}
psOutput = append(psOutput, params)
}
return sortPodPsOutput(opts.Sort, psOutput)
}
func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error {
if len(pods) == 0 && opts.Format != formats.JSONString {
return nil
}
psOutput, err := getAndSortPodJSONParams(pods, opts)
if err != nil {
return err
}
var out formats.Writer
switch opts.Format {
case formats.JSONString:
out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)}
default:
psOutput, err := getPodTemplateOutput(psOutput, opts)
if err != nil {
return errors.Wrapf(err, "unable to create output")
}
out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()}
}
return out.Out()
}