Add 'kpod ps' command
kpod ps lists the containers currently stored Displays the list of containers Signed-off-by: umohnani8 <umohnani@redhat.com>
This commit is contained in:
parent
c67859731f
commit
35ca80abe6
10 changed files with 1001 additions and 24 deletions
|
@ -3,16 +3,21 @@ package formats
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ghodss/yaml"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// JSONString const to save on duplicate variable names
|
||||
const JSONString string = "json"
|
||||
const (
|
||||
// JSONString const to save on duplicate variable names
|
||||
JSONString = "json"
|
||||
// IDString const to save on duplicates for Go templates
|
||||
IDString = "{{.ID}}"
|
||||
)
|
||||
|
||||
// Writer interface for outputs
|
||||
type Writer interface {
|
||||
|
@ -60,33 +65,34 @@ func (j JSONStructArray) Out() error {
|
|||
|
||||
// Out method for Go templates
|
||||
func (t StdoutTemplateArray) Out() error {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||
if strings.HasPrefix(t.Template, "table") {
|
||||
t.Template = strings.TrimSpace(t.Template[5:])
|
||||
// replace any spaces with tabs in template so that tabwriter can align it
|
||||
t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1)
|
||||
headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Template parsing error")
|
||||
}
|
||||
err = headerTmpl.Execute(os.Stdout, t.Fields)
|
||||
err = headerTmpl.Execute(w, t.Fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
t.Template = strings.Replace(t.Template, " ", "\t", -1)
|
||||
tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Template parsing error")
|
||||
}
|
||||
|
||||
for _, img := range t.Output {
|
||||
basicTmpl := tmpl.Funcs(basicFunctions)
|
||||
err = basicTmpl.Execute(os.Stdout, img)
|
||||
err = basicTmpl.Execute(w, img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
return nil
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// Out method for JSON struct
|
||||
|
|
|
@ -124,7 +124,7 @@ func historyCmd(c *cli.Context) error {
|
|||
|
||||
func genHistoryFormat(quiet, truncate, human bool) (format string) {
|
||||
if quiet {
|
||||
return "{{.ID}}"
|
||||
return formats.IDString
|
||||
}
|
||||
|
||||
if truncate {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
|
@ -110,7 +109,7 @@ func imagesCmd(c *cli.Context) error {
|
|||
|
||||
func genImagesFormat(quiet, truncate, digests bool) (format string) {
|
||||
if quiet {
|
||||
return "{{.ID}}"
|
||||
return formats.IDString
|
||||
}
|
||||
if truncate {
|
||||
format = "table {{ .ID | printf \"%-20.12s\" }} "
|
||||
|
@ -197,7 +196,7 @@ func (i *imageOutputParams) headerMap() map[string]string {
|
|||
if value == "ID" || value == "Name" {
|
||||
value = "Image" + value
|
||||
}
|
||||
values[key] = fmt.Sprintf("%s ", strings.ToUpper(splitCamelCase(value)))
|
||||
values[key] = strings.ToUpper(splitCamelCase(value))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ func main() {
|
|||
loadCommand,
|
||||
logsCommand,
|
||||
mountCommand,
|
||||
psCommand,
|
||||
pullCommand,
|
||||
pushCommand,
|
||||
renameCommand,
|
||||
|
|
|
@ -52,7 +52,7 @@ type jsonMountPoint struct {
|
|||
func mountCmd(c *cli.Context) error {
|
||||
formats := map[string]bool{
|
||||
"": true,
|
||||
"json": true,
|
||||
of.JSONString: true,
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
|
|
547
cmd/kpod/ps.go
Normal file
547
cmd/kpod/ps.go
Normal file
|
@ -0,0 +1,547 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
|
||||
"github.com/kubernetes-incubator/cri-o/libkpod"
|
||||
"github.com/kubernetes-incubator/cri-o/oci"
|
||||
"github.com/pkg/errors"
|
||||
"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
|
||||
}
|
||||
|
||||
type psOutputParams struct {
|
||||
ID string `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Command string `json:"command"`
|
||||
CreatedAt string `json:"created"`
|
||||
RunningFor string `json:"running"`
|
||||
Status string `json:"status"`
|
||||
Ports string `json:"ports"`
|
||||
Size string `json:"size"`
|
||||
Names string `json:"names"`
|
||||
Labels string `json:"labels"`
|
||||
Mounts string `json:"mounts"`
|
||||
}
|
||||
|
||||
const runningState = "running"
|
||||
|
||||
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)",
|
||||
},
|
||||
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",
|
||||
},
|
||||
}
|
||||
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 {
|
||||
config, err := getConfig(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get config")
|
||||
}
|
||||
server, err := libkpod.New(config)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating server")
|
||||
}
|
||||
if err := server.Update(); err != nil {
|
||||
return errors.Wrapf(err, "error updating list of containers")
|
||||
}
|
||||
|
||||
if len(c.Args()) > 0 {
|
||||
return errors.Errorf("too many arguments, ps takes no arguments")
|
||||
}
|
||||
|
||||
format := genPsFormat(c.Bool("quiet"), c.Bool("size"))
|
||||
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"),
|
||||
}
|
||||
|
||||
// all, latest, and last are mutually exclusive. Only one flag can be used at a time
|
||||
exclusiveOpts := 0
|
||||
if opts.last > 0 {
|
||||
exclusiveOpts++
|
||||
}
|
||||
if opts.latest {
|
||||
exclusiveOpts++
|
||||
}
|
||||
if opts.all {
|
||||
exclusiveOpts++
|
||||
}
|
||||
if exclusiveOpts > 1 {
|
||||
return errors.Errorf("Last, latest and all are mutually exclusive")
|
||||
}
|
||||
|
||||
containers, err := server.ListContainers()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting containers from server")
|
||||
}
|
||||
var params *FilterParamsPS
|
||||
if opts.filter != "" {
|
||||
params, err = parseFilter(opts.filter, containers)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing filter")
|
||||
}
|
||||
} else {
|
||||
params = nil
|
||||
}
|
||||
|
||||
containerList := getContainersMatchingFilter(containers, params, server)
|
||||
|
||||
return psOutput(containerList, server, opts)
|
||||
}
|
||||
|
||||
// generate the template based on conditions given
|
||||
func genPsFormat(quiet, size bool) (format string) {
|
||||
if quiet {
|
||||
return formats.IDString
|
||||
}
|
||||
format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t"
|
||||
if size {
|
||||
format += "{{.Size}}\t"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func psToGeneric(params []psOutputParams) []interface{} {
|
||||
genericParams := make([]interface{}, len(params))
|
||||
for i, v := range params {
|
||||
genericParams[i] = interface{}(v)
|
||||
}
|
||||
return genericParams
|
||||
}
|
||||
|
||||
// generate the accurate header based on template given
|
||||
func (p *psOutputParams) 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 == runningState {
|
||||
containersOutput = append(containersOutput, ctr)
|
||||
}
|
||||
}
|
||||
return containersOutput
|
||||
}
|
||||
|
||||
func psOutput(containers []*libkpod.ContainerData, server *libkpod.ContainerServer, opts psOptions) error {
|
||||
var (
|
||||
output []psOutputParams
|
||||
containersOutput []*libkpod.ContainerData
|
||||
status string
|
||||
ctrID string
|
||||
command string
|
||||
runningFor string
|
||||
imageName string
|
||||
mounts string
|
||||
ports string
|
||||
size string
|
||||
labels string
|
||||
createdAt string
|
||||
)
|
||||
|
||||
if len(containers) == 0 {
|
||||
fmt.Println("hereee")
|
||||
return nil
|
||||
}
|
||||
containersOutput = getContainers(containers, opts)
|
||||
|
||||
for _, ctr := range containersOutput {
|
||||
ctrID = ctr.ID
|
||||
runningFor = units.HumanDuration(time.Since(ctr.State.Created))
|
||||
createdAt = runningFor + " ago"
|
||||
command = getCommand(ctr.ImageCreatedBy)
|
||||
imageName = ctr.FromImage
|
||||
mounts = getMounts(ctr.Mounts, opts.noTrunc)
|
||||
ports = getPorts(ctr.Config.ExposedPorts)
|
||||
size = units.HumanSize(float64(ctr.SizeRootFs))
|
||||
labels = getLabels(ctr.Labels)
|
||||
|
||||
switch ctr.State.Status {
|
||||
case "stopped":
|
||||
status = "Exited (" + strconv.FormatInt(int64(ctr.State.ExitCode), 10) + ") " + runningFor + " ago"
|
||||
case runningState:
|
||||
status = "Up " + runningFor + " ago"
|
||||
default:
|
||||
status = "Created"
|
||||
}
|
||||
|
||||
if !opts.noTrunc {
|
||||
ctrID = ctr.ID[:idTruncLength]
|
||||
imageName = getImageName(ctr.FromImage)
|
||||
}
|
||||
|
||||
params := psOutputParams{
|
||||
ID: ctrID,
|
||||
Image: imageName,
|
||||
Command: command,
|
||||
CreatedAt: createdAt,
|
||||
RunningFor: runningFor,
|
||||
Status: status,
|
||||
Ports: ports,
|
||||
Size: size,
|
||||
Names: ctr.Name,
|
||||
Labels: labels,
|
||||
Mounts: mounts,
|
||||
}
|
||||
output = append(output, params)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out formats.Writer
|
||||
|
||||
switch opts.format {
|
||||
case formats.JSONString:
|
||||
out = formats.JSONStructArray{Output: psToGeneric(output)}
|
||||
default:
|
||||
out = formats.StdoutTemplateArray{Output: psToGeneric(output), Template: opts.format, Fields: output[0].headerMap()}
|
||||
}
|
||||
|
||||
return formats.Writer(out).Out()
|
||||
}
|
||||
|
||||
// getCommand gets the actual command from the whole command
|
||||
func getCommand(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 getLabels(labels fields.Set) 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
|
||||
}
|
|
@ -327,6 +327,22 @@ _kpod_export() {
|
|||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_kpod_ps() {
|
||||
local options_with_args="
|
||||
--filter -f
|
||||
--format
|
||||
--last -n
|
||||
"
|
||||
local boolean_options="
|
||||
--all -a
|
||||
--latest -l
|
||||
--no-trunc
|
||||
--quiet -q
|
||||
--size -s
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
||||
_complete_() {
|
||||
local options_with_args=$1
|
||||
local boolean_options="$2 -h --help"
|
||||
|
@ -377,6 +393,7 @@ _kpod_kpod() {
|
|||
load
|
||||
logs
|
||||
mount
|
||||
ps
|
||||
pull
|
||||
push
|
||||
rename
|
||||
|
|
|
@ -18,10 +18,11 @@ The **--quiet** flag displays the ID of the image only when set and the **--form
|
|||
flag is used to print the information using the Go template provided by the user.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| **Placeholder** | **Description** |
|
||||
|-----------------|------------------------------------------------------------------------------|
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| .ID | Image ID |
|
||||
| .Created | if **--human**, time elapsed since creation, otherwise time stamp of creation|
|
||||
| .Created | if **--human**, time elapsed since creation, otherwise time stamp of creation |
|
||||
| .CreatedBy | Command used to create the layer |
|
||||
| .Size | Size of layer on disk |
|
||||
| .Comment | Comment for the layer |
|
||||
|
|
119
docs/kpod-ps.1.md
Normal file
119
docs/kpod-ps.1.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
% kpod(1) kpod-ps - Simple tool to list containers
|
||||
% Urvashi Mohnani
|
||||
% kpod-ps "1" "AUGUST 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-ps - Prints out information about containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod ps [OPTIONS] CONTAINER**
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod ps** lists the running containers on the system. Use the **--all** flag to view
|
||||
all the containers information. By default it lists:
|
||||
|
||||
* container id
|
||||
* the name of the image the container is using
|
||||
* the COMMAND the container is executing
|
||||
* the time the container was created
|
||||
* the status of the container
|
||||
* port mappings the container is using
|
||||
* alternative names for the container
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod [GLOBAL OPTIONS] ps [OPTIONS]**
|
||||
|
||||
## GLOBAL OPTIONS
|
||||
|
||||
**--help, -h**
|
||||
Print usage statement
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--all, -a**
|
||||
Show all the containers, default is only running containers
|
||||
|
||||
**--no-trunc**
|
||||
Display the extended information
|
||||
|
||||
**--quiet, -q**
|
||||
Print the numeric IDs of the containers only
|
||||
|
||||
**--format**
|
||||
Pretty-print containers to JSON or using a Go template
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| **Placeholder** | **Description** |
|
||||
| --------------- | ------------------------------------------------ |
|
||||
| .ID | Container ID |
|
||||
| .Image | Image ID/Name |
|
||||
| .Command | Quoted command used |
|
||||
| .CreatedAt | Creation time for container |
|
||||
| .RunningFor | Time elapsed since container was started |
|
||||
| .Status | Status of container |
|
||||
| .Ports | Exposed ports |
|
||||
| .Size | Size of container |
|
||||
| .Names | Name of container |
|
||||
| .Labels | All the labels assigned to the container |
|
||||
| .Label | Value of the specific label provided by the user |
|
||||
| .Mounts | Volumes mounted in the container |
|
||||
|
||||
|
||||
**--size, -s**
|
||||
Display the total file size
|
||||
|
||||
**--last, -n**
|
||||
Print the n last created containers (all states)
|
||||
|
||||
**--latest, -l**
|
||||
show the latest container created (all states)
|
||||
|
||||
**--filter, -f**
|
||||
Filter output based on conditions given
|
||||
|
||||
Valid filters are listed below:
|
||||
|
||||
| **Filter** | **Description** |
|
||||
| --------------- | ------------------------------------------------------------------- |
|
||||
| id | [ID] Container's ID |
|
||||
| name | [Name] Container's name |
|
||||
| label | [Key] or [Key=Value] Label assigned to a container |
|
||||
| exited | [Int] Container's exit code |
|
||||
| status | [Status] Container's status, e.g *running*, *stopped* |
|
||||
| ancestor | [ImageName] Image or descendant used to create container |
|
||||
| before | [ID] or [Name] Containers created before this container |
|
||||
| since | [ID] or [Name] Containers created since this container |
|
||||
| volume | [VolumeName] or [MountpointDestination] Volume mounted in container |
|
||||
|
||||
## COMMANDS
|
||||
|
||||
```
|
||||
sudo kpod ps -a
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
02f65160e14ca redis:alpine "redis-server" 19 hours ago Exited (-1) 19 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0
|
||||
69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1
|
||||
```
|
||||
|
||||
```
|
||||
sudo kpod ps -a -s
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
||||
02f65160e14ca redis:alpine "redis-server" 20 hours ago Exited (-1) 20 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0 27.49 MB
|
||||
69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1 27.49 MB
|
||||
```
|
||||
|
||||
```
|
||||
sudo kpod ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
|
||||
02f65160e14ca redis:alpine tier=backend proc,tmpfs,devpts,shm,mqueue,sysfs,cgroup,/var/run/,/var/run/
|
||||
69ed779d8ef9f redis:alpine batch=no,type=small proc,tmpfs,devpts,shm,mqueue,sysfs,cgroup,/var/run/,/var/run/
|
||||
```
|
||||
|
||||
## ps
|
||||
Print a list of containers
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
287
test/kpod_ps.bats
Normal file
287
test/kpod_ps.bats
Normal file
|
@ -0,0 +1,287 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
IMAGE="redis:alpine"
|
||||
ROOT="$TESTDIR/crio"
|
||||
RUNROOT="$TESTDIR/crio-run"
|
||||
KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT ${STORAGE_OPTS}"
|
||||
|
||||
@test "kpod ps with no containers" {
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps default" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
ctr_id="$output"
|
||||
run crioctl ctr start --id "$ctr_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps all flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps --all
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps size flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a -s
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --size
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps quiet flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a -q
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --quiet
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps latest flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps --latest
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -l
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps last flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps --last 2
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -n 2
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps no-trunc flag" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --no-trunc
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps format flag = json" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --format json | python -m json.tool"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps format flag = go template" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --format "table {{.ID}} {{.Image}} {{.Labels}}"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps filter flag - ancestor" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter ancestor=${IMAGE}
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps filter flag - id" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
ctr_id="$output"
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter id="$ctr_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod ps filter flag - status" {
|
||||
start_crio
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
pod_id="$output"
|
||||
run crioctl image pull "$IMAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
ctr_id="$output"
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter status=running
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
cleanup_ctrs
|
||||
cleanup_pods
|
||||
stop_crio
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
Loading…
Reference in a new issue