From 342ce4db50ed89605bbdd5f8ac30c3db08e15a7e Mon Sep 17 00:00:00 2001 From: baude Date: Tue, 21 Nov 2017 14:12:21 -0600 Subject: kpod ps Wire up kpod ps with the new libpod container backend. Signed-off-by: baude Closes: #67 Approved by: rhatdan --- cmd/kpod/ps.go | 314 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 222 insertions(+), 92 deletions(-) (limited to 'cmd/kpod/ps.go') diff --git a/cmd/kpod/ps.go b/cmd/kpod/ps.go index a5f64ed11..13d72429e 100644 --- a/cmd/kpod/ps.go +++ b/cmd/kpod/ps.go @@ -15,9 +15,11 @@ import ( "k8s.io/apimachinery/pkg/fields" + "fmt" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/kpod/formats" "github.com/projectatomic/libpod/libkpod" + "github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/oci" "github.com/urfave/cli" ) @@ -144,18 +146,19 @@ func psCmd(c *cli.Context) error { if err := validateFlags(c, psFlags); err != nil { return err } - config, err := getConfig(c) - if err != nil { - return errors.Wrapf(err, "could not get config") + + // latest, and last are mutually exclusive. + if c.Int("last") >= 0 && c.Bool("latest") { + return errors.Errorf("last and latest are mutually exclusive") } - server, err := libkpod.New(config) + + runtime, err := getRuntime(c) 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") + return errors.Wrapf(err, "error creating libpod runtime") } + defer runtime.Shutdown(false) + if len(c.Args()) > 0 { return errors.Errorf("too many arguments, ps takes no arguments") } @@ -177,38 +180,128 @@ func psCmd(c *cli.Context) error { namespace: c.Bool("namespace"), } - // 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") + var filterFuncs []libpod.ContainerFilter + // When we are dealing with latest or last=n, we need to + // get all containers. + if !opts.all && !opts.latest && opts.last < 1 { + // only get running containers + filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { + state, _ := c.State() + return state == libpod.ContainerStateRunning + }) } - 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") + filters := strings.Split(opts.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 := generateContainerFilterFuncs(filterSplit[0], filterSplit[1], runtime) + if err != nil { + return errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) } + } + + containers, err := runtime.GetContainers(filterFuncs...) + var outputContainers []*libpod.Container + if opts.latest && len(containers) > 0 { + outputContainers = append(outputContainers, containers[0]) + } else if opts.last > 0 && opts.last <= len(containers) { + outputContainers = append(outputContainers, containers[:opts.last]...) } else { - params = nil + outputContainers = containers } - containerList := getContainersMatchingFilter(containers, params, server) + return generatePsOutput(outputContainers, opts) +} - return generatePsOutput(containerList, server, opts) +func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(container *libpod.Container) bool, error) { + switch filter { + case "id": + return func(c *libpod.Container) bool { + return c.ID() == filterValue + }, nil + case "label": + return func(c *libpod.Container) bool { + for _, label := range c.Labels() { + if label == filterValue { + return true + } + } + return false + }, nil + case "name": + return func(c *libpod.Container) bool { + return c.Name() == filterValue + }, nil + case "exited": + exitCode, err := strconv.ParseInt(filterValue, 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) + } + return func(c *libpod.Container) bool { + ec, err := c.ExitCode() + if ec == int32(exitCode) && err == nil { + return true + } + return false + }, nil + case "status": + if !libpod.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(c *libpod.Container) bool { + status, err := c.State() + if err != nil { + return false + } + return status.String() == filterValue + }, nil + case "ancestor": + // This needs to refine to match docker + // - ancestor=([:tag]|| ⟨image@digest⟩) - containers created from an image or a descendant. + return func(c *libpod.Container) bool { + containerConfig := c.Config() + if containerConfig.RootfsImageID == filterValue || containerConfig.RootfsImageName == filterValue { + return true + } + return false + }, nil + case "before": + ctr, err := runtime.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.After(cc.CreatedTime) + }, nil + case "since": + ctr, err := runtime.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.Before(cc.CreatedTime) + }, nil + case "volume": + //- volume=(|) + return func(c *libpod.Container) bool { + containerConfig := c.Config() + //TODO We need to still lookup against volumes too + return containerConfig.MountLabel == filterValue + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) } // generate the template based on conditions given @@ -280,37 +373,60 @@ func getContainers(containers []*libkpod.ContainerData, opts psOptions) []*libkp } // getTemplateOutput returns the modified container information -func getTemplateOutput(containers []*libkpod.ContainerData, opts psOptions) (psOutput []psTemplateParams) { +func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemplateParams, error) { + var psOutput []psTemplateParams var status string for _, ctr := range containers { - ctrID := ctr.ID - runningFor := units.HumanDuration(time.Since(ctr.State.Created)) + ctrID := ctr.ID() + conConfig := ctr.Config() + conState, err := ctr.State() + if err != nil { + return psOutput, errors.Wrapf(err, "unable to obtain container state") + } + exitCode, err := ctr.ExitCode() + if err != nil { + return psOutput, errors.Wrapf(err, "unable to obtain container exit code") + } + pid, err := ctr.PID() + if err != nil { + return psOutput, errors.Wrapf(err, "unable to obtain container pid") + } + runningFor := units.HumanDuration(time.Since(conConfig.CreatedTime)) createdAt := runningFor + " ago" - 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 oci.ContainerStateStopped: - status = "Exited (" + strconv.FormatInt(int64(ctr.State.ExitCode), 10) + ") " + runningFor + " ago" - case oci.ContainerStateRunning: + imageName := conConfig.RootfsImageName + + // TODO We currently dont have the ability to get many of + // these data items. Uncomment as progress is made + + //command := getStrFromSquareBrackets(ctr.ImageCreatedBy) + command := strings.Join(ctr.Spec().Process.Args, " ") + //mounts := getMounts(ctr.Mounts, opts.noTrunc) + //ports := getPorts(ctr.Config.ExposedPorts) + //size := units.HumanSize(float64(ctr.SizeRootFs)) + labels := formatLabels(ctr.Labels()) + ns := getNamespaces(pid) + + switch conState { + case libpod.ContainerStateStopped: + status = fmt.Sprintf("Exited (%d) %s ago", exitCode, runningFor) + case libpod.ContainerStateRunning: status = "Up " + runningFor + " ago" - case oci.ContainerStatePaused: + case libpod.ContainerStatePaused: status = "Paused" - default: + case libpod.ContainerStateCreated: status = "Created" + default: + status = "Dead" } if !opts.noTrunc { - ctrID = ctr.ID[:idTruncLength] - imageName = getImageName(ctr.FromImage) + ctrID = ctr.ID()[:idTruncLength] + imageName = conConfig.RootfsImageName } + // TODO We currently dont have the ability to get many of + // these data items. Uncomment as progress is made + params := psTemplateParams{ ID: ctrID, Image: imageName, @@ -318,23 +434,23 @@ func getTemplateOutput(containers []*libkpod.ContainerData, opts psOptions) (psO CreatedAt: createdAt, RunningFor: runningFor, Status: status, - Ports: ports, - Size: size, - 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, + //Ports: ports, + //Size: size, + Names: ctr.Name(), + Labels: labels, + //Mounts: mounts, + PID: 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 + return psOutput, nil } func getNamespaces(pid int) *namespace { @@ -368,50 +484,64 @@ func getNamespaceInfo(path string) (string, error) { } // getJSONOutput returns the container info in its raw form -func getJSONOutput(containers []*libkpod.ContainerData, nSpace bool) (psOutput []psJSONParams) { +func getJSONOutput(containers []*libpod.Container, nSpace bool) ([]psJSONParams, error) { + var psOutput []psJSONParams var ns *namespace for _, ctr := range containers { + pid, err := ctr.PID() + if err != nil { + return psOutput, errors.Wrapf(err, "unable to obtain container pid") + } if nSpace { - ns = getNamespaces(ctr.State.Pid) + ns = getNamespaces(pid) + } + cc := ctr.Config() + conState, err := ctr.State() + if err != nil { + return psOutput, errors.Wrapf(err, "unable to obtain container state for JSON output") } - params := psJSONParams{ - ID: ctr.ID, - Image: ctr.FromImage, - ImageID: ctr.FromImageID, - Command: getStrFromSquareBrackets(ctr.ImageCreatedBy), - CreatedAt: ctr.State.Created, - RunningFor: time.Since(ctr.State.Created), - Status: ctr.State.Status, - Ports: ctr.Config.ExposedPorts, - Size: ctr.SizeRootFs, - Names: ctr.Name, - Labels: ctr.Labels, - Mounts: ctr.Mounts, - ContainerRunning: ctr.State.Status == oci.ContainerStateRunning, + // TODO When we have ability to obtain the commented out data, we need + // TODO to add it + ID: ctr.ID(), + Image: cc.RootfsImageName, + ImageID: cc.RootfsImageID, + //Command: getStrFromSquareBrackets(ctr.ImageCreatedBy), + Command: strings.Join(ctr.Spec().Process.Args, " "), + CreatedAt: cc.CreatedTime, + RunningFor: time.Since(cc.CreatedTime), + Status: conState.String(), + //Ports: cc.Spec.Linux.Resources.Network. + //Size: ctr.SizeRootFs, + Names: cc.Name, + Labels: cc.Labels, + Mounts: cc.Spec.Mounts, + ContainerRunning: conState.String() == oci.ContainerStateRunning, Namespaces: ns, } psOutput = append(psOutput, params) } - return + return psOutput, nil } -func generatePsOutput(containers []*libkpod.ContainerData, server *libkpod.ContainerServer, opts psOptions) error { - containersOutput := getContainers(containers, opts) - // In the case of JSON, we want to continue so we at least pass - // {} --valid JSON-- to the consumer - if len(containersOutput) == 0 && opts.format != formats.JSONString { +func generatePsOutput(containers []*libpod.Container, opts psOptions) error { + if len(containers) == 0 && opts.format != formats.JSONString { return nil } - var out formats.Writer switch opts.format { case formats.JSONString: - psOutput := getJSONOutput(containersOutput, opts.namespace) + psOutput, err := getJSONOutput(containers, opts.namespace) + if err != nil { + return errors.Wrapf(err, "unable to create JSON for output") + } out = formats.JSONStructArray{Output: psToGeneric([]psTemplateParams{}, psOutput)} default: - psOutput := getTemplateOutput(containersOutput, opts) + psOutput, err := getTemplateOutput(containers, opts) + if err != nil { + return errors.Wrapf(err, "unable to create output") + } out = formats.StdoutTemplateArray{Output: psToGeneric(psOutput, []psJSONParams{}), Template: opts.format, Fields: psOutput[0].headerMap()} } @@ -440,7 +570,7 @@ func getImageName(img string) string { } // getLabels converts the labels to a string of the form "key=value, key2=value2" -func getLabels(labels fields.Set) string { +func formatLabels(labels map[string]string) string { var arr []string if len(labels) > 0 { for key, val := range labels { -- cgit v1.2.3-54-g00ecf