566 lines
15 KiB
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()
|
|
}
|