diff --git a/cmd/kpod/ps.go b/cmd/kpod/ps.go index b28a4aa6..3f08a4e7 100644 --- a/cmd/kpod/ps.go +++ b/cmd/kpod/ps.go @@ -1,6 +1,8 @@ package main import ( + "os" + "path/filepath" "reflect" "regexp" "strconv" @@ -21,15 +23,16 @@ import ( ) type psOptions struct { - all bool - filter string - format string - last int - latest bool - noTrunc bool - quiet bool - size bool - label string + all bool + filter string + format string + last int + latest bool + noTrunc bool + quiet bool + size bool + label string + namespace bool } type psTemplateParams struct { @@ -44,6 +47,14 @@ type psTemplateParams struct { Names string Labels string Mounts string + PID int + Cgroup string + IPC string + MNT string + NET string + PIDNS string + User string + UTS string } // psJSONParams is only used when the JSON format is specified, @@ -64,6 +75,18 @@ type psJSONParams struct { Labels fields.Set `json:"labels"` Mounts []specs.Mount `json:"mounts"` ContainerRunning bool `json:"ctrRunning"` + Namespaces namespace `json:"namespace,omitempty"` +} + +type namespace struct { + PID string `json:"ctrPID,omitempty"` + Cgroup string `json:"cgroup,omitempty"` + IPC string `json:"ipc,omitempty"` + MNT string `json:"mnt,omitempty"` + NET string `json:"net,omitempty"` + PIDNS string `json:"pid,omitempty"` + User string `json:"user,omitempty"` + UTS string `json:"uts,omitempty"` } const runningState = "running" @@ -103,6 +126,10 @@ var ( Name: "size, s", Usage: "Display the total file sizes", }, + cli.BoolFlag{ + Name: "namespace, ns", + Usage: "Display namespace information", + }, } psDescription = "Prints out information about the containers" psCommand = cli.Command{ @@ -132,20 +159,21 @@ func psCmd(c *cli.Context) error { return errors.Errorf("too many arguments, ps takes no arguments") } - format := genPsFormat(c.Bool("quiet"), c.Bool("size")) + format := genPsFormat(c.Bool("quiet"), c.Bool("size"), c.Bool("namespace")) if c.IsSet("format") { format = c.String("format") } opts := psOptions{ - all: c.Bool("all"), - filter: c.String("filter"), - format: format, - last: c.Int("last"), - latest: c.Bool("latest"), - noTrunc: c.Bool("no-trunc"), - quiet: c.Bool("quiet"), - size: c.Bool("size"), + all: c.Bool("all"), + filter: c.String("filter"), + format: format, + last: c.Int("last"), + latest: c.Bool("latest"), + noTrunc: c.Bool("no-trunc"), + quiet: c.Bool("quiet"), + size: c.Bool("size"), + namespace: c.Bool("namespace"), } // all, latest, and last are mutually exclusive. Only one flag can be used at a time @@ -183,10 +211,14 @@ func psCmd(c *cli.Context) error { } // generate the template based on conditions given -func genPsFormat(quiet, size bool) (format string) { +func genPsFormat(quiet, size, namespace bool) (format string) { if quiet { return formats.IDString } + if namespace { + format = "table {{.ID}}\t{{.Names}}\t{{.PID}}\t{{.Cgroup}}\t{{.IPC}}\t{{.MNT}}\t{{.NET}}\t{{.PIDNS}}\t{{.User}}\t{{.UTS}}\t" + return + } format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t" if size { format += "{{.Size}}\t" @@ -253,13 +285,15 @@ func getTemplateOutput(containers []*libkpod.ContainerData, opts psOptions) (psO ctrID := ctr.ID runningFor := units.HumanDuration(time.Since(ctr.State.Created)) createdAt := runningFor + " ago" - command := getCommand(ctr.ImageCreatedBy) + command := getStrFromSquareBrackets(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) + ns := getNamespaces(ctr.State.Pid) + switch ctr.State.Status { case "stopped": status = "Exited (" + strconv.FormatInt(int64(ctr.State.ExitCode), 10) + ") " + runningFor + " ago" @@ -286,20 +320,63 @@ func getTemplateOutput(containers []*libkpod.ContainerData, opts psOptions) (psO Names: ctr.Name, Labels: labels, Mounts: mounts, + PID: ctr.State.Pid, + Cgroup: ns.Cgroup, + IPC: ns.IPC, + MNT: ns.MNT, + NET: ns.NET, + PIDNS: ns.PID, + User: ns.User, + UTS: ns.UTS, } psOutput = append(psOutput, params) } return } +func getNamespaces(pid int) namespace { + ctrPID := strconv.Itoa(pid) + cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) + ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) + mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) + net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) + pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) + user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) + uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) + + return namespace{ + PID: ctrPID, + Cgroup: cgroup, + IPC: ipc, + MNT: mnt, + NET: net, + PIDNS: pidns, + User: user, + UTS: uts, + } +} + +func getNamespaceInfo(path string) (string, error) { + val, err := os.Readlink(path) + if err != nil { + return "", errors.Wrapf(err, "error getting info from %q", path) + } + return getStrFromSquareBrackets(val), nil +} + // getJSONOutput returns the container info in its raw form -func getJSONOutput(containers []*libkpod.ContainerData) (psOutput []psJSONParams) { +func getJSONOutput(containers []*libkpod.ContainerData, nSpace bool) (psOutput []psJSONParams) { + var ns namespace for _, ctr := range containers { + if nSpace { + ns = getNamespaces(ctr.State.Pid) + } + params := psJSONParams{ ID: ctr.ID, Image: ctr.FromImage, ImageID: ctr.FromImageID, - Command: getCommand(ctr.ImageCreatedBy), + Command: getStrFromSquareBrackets(ctr.ImageCreatedBy), CreatedAt: ctr.State.Created, RunningFor: time.Since(ctr.State.Created), Status: ctr.State.Status, @@ -309,6 +386,7 @@ func getJSONOutput(containers []*libkpod.ContainerData) (psOutput []psJSONParams Labels: ctr.Labels, Mounts: ctr.Mounts, ContainerRunning: ctr.State.Status == runningState, + Namespaces: ns, } psOutput = append(psOutput, params) } @@ -325,7 +403,7 @@ func generatePsOutput(containers []*libkpod.ContainerData, server *libkpod.Conta switch opts.format { case formats.JSONString: - psOutput := getJSONOutput(containersOutput) + psOutput := getJSONOutput(containersOutput, opts.namespace) out = formats.JSONStructArray{Output: psToGeneric([]psTemplateParams{}, psOutput)} default: psOutput := getTemplateOutput(containersOutput, opts) @@ -335,8 +413,8 @@ func generatePsOutput(containers []*libkpod.ContainerData, server *libkpod.Conta return formats.Writer(out).Out() } -// getCommand gets the actual command from the whole command -func getCommand(cmd string) string { +// getStrFromSquareBrackets gets the string inside [] from a string +func getStrFromSquareBrackets(cmd string) string { reg, err := regexp.Compile(".*\\[|\\].*") if err != nil { return "" diff --git a/completions/bash/kpod b/completions/bash/kpod index 1ebf6c3e..4a0327c5 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -361,6 +361,7 @@ _kpod_ps() { --no-trunc --quiet -q --size -s + --namespace --ns " _complete_ "$options_with_args" "$boolean_options" } diff --git a/docs/kpod-ps.1.md b/docs/kpod-ps.1.md index 027c9bdd..08e65a4d 100644 --- a/docs/kpod-ps.1.md +++ b/docs/kpod-ps.1.md @@ -69,6 +69,9 @@ Valid placeholders for the Go template are listed below: **--latest, -l** show the latest container created (all states) +**--namespace, --ns** + Display namespace information + **--filter, -f** Filter output based on conditions given @@ -108,6 +111,14 @@ sudo kpod ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" 69ed779d8ef9f redis:alpine batch=no,type=small proc,tmpfs,devpts,shm,mqueue,sysfs,cgroup,/var/run/,/var/run/ ``` +``` +sudo kpod ps --ns -a +CONTAINER ID NAMES PID CGROUP IPC MNT NET PIDNS USER UTS +3557d882a82e3 k8s_container2_podsandbox1_redhat.test.crio_redhat-test-crio_1 29910 4026531835 4026532585 4026532593 4026532508 4026532595 4026531837 4026532594 +09564cdae0bec k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1 29851 4026531835 4026532585 4026532590 4026532508 4026532592 4026531837 4026532591 +a31ebbee9cee7 k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0 29717 4026531835 4026532585 4026532587 4026532508 4026532589 4026531837 4026532588 +``` + ## ps Print a list of containers diff --git a/test/kpod_ps.bats b/test/kpod_ps.bats index 5954be6b..bbc01d32 100644 --- a/test/kpod_ps.bats +++ b/test/kpod_ps.bats @@ -179,6 +179,30 @@ KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT ${STORAGE_OPTS}" [ "$status" -eq 0 ] } +@test "kpod ps namespace 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 --ns + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} ps --all --namespace + 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 ] @@ -191,7 +215,7 @@ KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT ${STORAGE_OPTS}" 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" + run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --ns --format json | python -m json.tool" echo "$output" [ "$status" -eq 0 ] cleanup_ctrs