diff --git a/cmd/kpod/formats/formats.go b/cmd/kpod/formats/formats.go index 7302f75f..007f09c6 100644 --- a/cmd/kpod/formats/formats.go +++ b/cmd/kpod/formats/formats.go @@ -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 diff --git a/cmd/kpod/history.go b/cmd/kpod/history.go index 44521734..4c83213c 100644 --- a/cmd/kpod/history.go +++ b/cmd/kpod/history.go @@ -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 { diff --git a/cmd/kpod/images.go b/cmd/kpod/images.go index a1194eb7..375e7cfe 100644 --- a/cmd/kpod/images.go +++ b/cmd/kpod/images.go @@ -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\" }} " @@ -201,7 +200,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 } diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 937c1d76..f8a5a1f5 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -28,6 +28,7 @@ func main() { loadCommand, logsCommand, mountCommand, + psCommand, pullCommand, pushCommand, renameCommand, diff --git a/cmd/kpod/mount.go b/cmd/kpod/mount.go index 108610a4..7b46ff14 100644 --- a/cmd/kpod/mount.go +++ b/cmd/kpod/mount.go @@ -51,8 +51,8 @@ type jsonMountPoint struct { func mountCmd(c *cli.Context) error { formats := map[string]bool{ - "": true, - "json": true, + "": true, + of.JSONString: true, } args := c.Args() diff --git a/cmd/kpod/ps.go b/cmd/kpod/ps.go new file mode 100644 index 00000000..ae46e0eb --- /dev/null +++ b/cmd/kpod/ps.go @@ -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 +} diff --git a/completions/bash/kpod b/completions/bash/kpod index ee7f037c..3893c9dc 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -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 diff --git a/docs/kpod-history.1.md b/docs/kpod-history.1.md index 26fdbdae..713da6f5 100644 --- a/docs/kpod-history.1.md +++ b/docs/kpod-history.1.md @@ -18,13 +18,14 @@ 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| -| .CreatedBy | Command used to create the layer | -| .Size | Size of layer on disk | -| .Comment | Comment for the layer | + +| **Placeholder** | **Description** | +| --------------- | ----------------------------------------------------------------------------- | +| .ID | Image ID | +| .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 | **kpod [GLOBAL OPTIONS]** diff --git a/docs/kpod-ps.1.md b/docs/kpod-ps.1.md new file mode 100644 index 00000000..798ed1c6 --- /dev/null +++ b/docs/kpod-ps.1.md @@ -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 diff --git a/test/kpod_ps.bats b/test/kpod_ps.bats new file mode 100644 index 00000000..5954be6b --- /dev/null +++ b/test/kpod_ps.bats @@ -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 ] +}