From 23cd1928ec36a80f491b329dda3789b2dac32bcc Mon Sep 17 00:00:00 2001 From: baude Date: Tue, 9 Apr 2019 13:07:27 -0500 Subject: podman-remote ps add the ability to run ps on containers using the remote client. Signed-off-by: baude --- cmd/podman/commands.go | 1 - cmd/podman/main.go | 1 + cmd/podman/ps.go | 183 ++--------------------------------- cmd/podman/shared/container.go | 171 +++++++++++++++++++++++++++++++- cmd/podman/varlink/io.podman.varlink | 43 ++++++++ libpod/container.go | 23 +++++ pkg/adapter/containers.go | 10 ++ pkg/adapter/containers_remote.go | 72 ++++++++++++++ pkg/varlinkapi/containers.go | 49 ++++++++++ pkg/varlinkapi/util.go | 33 +++++++ 10 files changed, 407 insertions(+), 179 deletions(-) diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index e9afcbc06..5b2581216 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -16,7 +16,6 @@ func getMainCommands() []*cobra.Command { _execCommand, _generateCommand, _playCommand, - &_psCommand, _loginCommand, _logoutCommand, _mountCommand, diff --git a/cmd/podman/main.go b/cmd/podman/main.go index b44cf9f0a..52bf2daf0 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -52,6 +52,7 @@ var mainCommands = []*cobra.Command{ _loadCommand, _logsCommand, podCommand.Command, + &_psCommand, _pullCommand, _pushCommand, &_rmiCommand, diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 759a03b86..59376b6aa 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -6,7 +6,6 @@ import ( "os" "reflect" "sort" - "strconv" "strings" "text/tabwriter" "time" @@ -14,15 +13,12 @@ import ( tm "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/pkg/adapter" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/fields" ) @@ -224,7 +220,7 @@ func psCmd(c *cliconfig.PsValues) error { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -279,128 +275,6 @@ func checkFlagsPassed(c *cliconfig.PsValues) error { return nil } -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 strings.Contains(c.ID(), filterValue) - }, nil - case "label": - var filterArray []string = strings.SplitN(filterValue, "=", 2) - var filterKey string = filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - return func(c *libpod.Container) bool { - for labelKey, labelValue := range c.Labels() { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - return true - } - } - return false - }, nil - case "name": - return func(c *libpod.Container) bool { - return strings.Contains(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, exited, err := c.ExitCode() - if ec == int32(exitCode) && err == nil && exited == true { - return true - } - return false - }, nil - case "status": - if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "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 - } - if filterValue == "stopped" { - filterValue = "exited" - } - state := status.String() - if status == libpod.ContainerStateConfigured { - state = "created" - } else if status == libpod.ContainerStateStopped { - state = "exited" - } - return state == 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 strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(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() - var dest string - arr := strings.Split(filterValue, ":") - source := arr[0] - if len(arr) == 2 { - dest = arr[1] - } - for _, mount := range containerConfig.Spec.Mounts { - if dest != "" && (mount.Source == source && mount.Destination == dest) { - return true - } - if dest == "" && mount.Source == source { - return true - } - } - return false - }, nil - case "health": - return func(c *libpod.Container) bool { - hcStatus, err := c.HealthCheckStatus() - if err != nil { - return false - } - return hcStatus == filterValue - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} - // generate the accurate header based on template given func (p *psTemplateParams) headerMap() map[string]string { v := reflect.Indirect(reflect.ValueOf(p)) @@ -550,11 +424,9 @@ func dumpJSON(containers []shared.PsContainerOutput) error { return nil } -func psDisplay(c *cliconfig.PsValues, runtime *libpod.Runtime) error { +func psDisplay(c *cliconfig.PsValues, runtime *adapter.LocalRuntime) error { var ( - filterFuncs []libpod.ContainerFilter - outputContainers []*libpod.Container - err error + err error ) opts := shared.PsOptions{ All: c.All, @@ -570,51 +442,8 @@ func psDisplay(c *cliconfig.PsValues, runtime *libpod.Runtime) error { Sync: c.Sync, } - maxWorkers := shared.Parallelize("ps") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks - } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - filters := c.Filter - if len(filters) > 0 { - for _, f := range filters { - filterSplit := strings.SplitN(f, "=", 2) - 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) - } - } - if !opts.Latest { - // Get all containers - containers, err := runtime.GetContainers(filterFuncs...) - if err != nil { - return err - } - - // We only want the last few containers - if opts.Last > 0 && opts.Last <= len(containers) { - return errors.Errorf("--last not yet supported") - } else { - outputContainers = containers - } - } else { - // Get just the latest container - // Ignore filters - latestCtr, err := runtime.GetLatestContainer() - if err != nil { - return err - } - - outputContainers = []*libpod.Container{latestCtr} - } - - pss := shared.PBatch(outputContainers, maxWorkers, opts) + pss, err := runtime.Ps(c, opts) + // Here and down if opts.Sort != "" { pss, err = sortPsOutput(opts.Sort, pss) if err != nil { diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 6826191c5..7bef62355 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -44,7 +44,6 @@ type PsOptions struct { Quiet bool Size bool Sort string - Label string Namespace bool Sync bool } @@ -274,6 +273,176 @@ func worker(wg *sync.WaitGroup, jobs <-chan workerInput, results chan<- PsContai } } +func generateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { + switch filter { + case "id": + return func(c *libpod.Container) bool { + return strings.Contains(c.ID(), filterValue) + }, nil + case "label": + var filterArray []string = strings.SplitN(filterValue, "=", 2) + var filterKey string = filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + return func(c *libpod.Container) bool { + for labelKey, labelValue := range c.Labels() { + if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { + return true + } + } + return false + }, nil + case "name": + return func(c *libpod.Container) bool { + return strings.Contains(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, exited, err := c.ExitCode() + if ec == int32(exitCode) && err == nil && exited == true { + return true + } + return false + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "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 + } + if filterValue == "stopped" { + filterValue = "exited" + } + state := status.String() + if status == libpod.ContainerStateConfigured { + state = "created" + } else if status == libpod.ContainerStateStopped { + state = "exited" + } + return state == 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 strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { + return true + } + return false + }, nil + case "before": + ctr, err := r.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 := r.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() + var dest string + arr := strings.Split(filterValue, ":") + source := arr[0] + if len(arr) == 2 { + dest = arr[1] + } + for _, mount := range containerConfig.Spec.Mounts { + if dest != "" && (mount.Source == source && mount.Destination == dest) { + return true + } + if dest == "" && mount.Source == source { + return true + } + } + return false + }, nil + case "health": + return func(c *libpod.Container) bool { + hcStatus, err := c.HealthCheckStatus() + if err != nil { + return false + } + return hcStatus == filterValue + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + +// GetPsContainerOutput returns a slice of containers specifically for ps output +func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, maxWorkers int) ([]PsContainerOutput, error) { + var ( + filterFuncs []libpod.ContainerFilter + outputContainers []*libpod.Container + ) + + if len(filters) > 0 { + for _, f := range filters { + filterSplit := strings.SplitN(f, "=", 2) + if len(filterSplit) < 2 { + return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + generatedFunc, err := generateContainerFilterFuncs(filterSplit[0], filterSplit[1], r) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) + } + } + if !opts.Latest { + // Get all containers + containers, err := r.GetContainers(filterFuncs...) + if err != nil { + return nil, err + } + + // We only want the last few containers + if opts.Last > 0 && opts.Last <= len(containers) { + return nil, errors.Errorf("--last not yet supported") + } else { + outputContainers = containers + } + } else { + // Get just the latest container + // Ignore filters + latestCtr, err := r.GetLatestContainer() + if err != nil { + return nil, err + } + + outputContainers = []*libpod.Container{latestCtr} + } + + pss := PBatch(outputContainers, maxWorkers, opts) + return pss, nil +} + // PBatch is performs batch operations on a container in parallel. It spawns the number of workers // relative to the the number of parallel operations desired. func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsContainerOutput { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 9098a9297..d8905326c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -133,6 +133,47 @@ type ContainerStats ( pids: int ) +type PsOpts ( + all: bool, + filters: ?[]string, + last: ?int, + latest: ?bool, + noTrunc: ?bool, + pod: ?bool, + quiet: ?bool, + sort: ?string, + sync: ?bool +) + +type PsContainer ( + id: string, + image: string, + command: string, + created: string, + ports: string, + names: string, + isInfra: bool, + status: string, + state: string, + pidNum: int, + rootFsSize: int, + rwSize: int, + pod: string, + createdAt: string, + exitedAt: string, + startedAt: string, + labels: [string]string, + nsPid: string, + cgroup: string, + ipc: string, + mnt: string, + net: string, + pidNs: string, + user: string, + uts: string, + mounts: string +) + # ContainerMount describes the struct for mounts in a container type ContainerMount ( destination: string, @@ -474,6 +515,8 @@ method GetInfo() -> (info: PodmanInfo) # See also [GetContainer](#GetContainer). method ListContainers() -> (containers: []Container) +method Ps(opts: PsOpts) -> (containers: []PsContainer) + # GetContainer returns information about a single container. If a container # with the given id doesn't exist, a [ContainerNotFound](#ContainerNotFound) # error will be returned. See also [ListContainers](ListContainers) and diff --git a/libpod/container.go b/libpod/container.go index 6d5e063ab..523e571b1 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -401,6 +401,29 @@ func (t ContainerStatus) String() string { return "bad state" } +// StringToContainerStatus converts a string representation of a containers +// status into an actual container status type +func StringToContainerStatus(status string) (ContainerStatus, error) { + switch status { + case ContainerStateUnknown.String(): + return ContainerStateUnknown, nil + case ContainerStateConfigured.String(): + return ContainerStateConfigured, nil + case ContainerStateCreated.String(): + return ContainerStateCreated, nil + case ContainerStateRunning.String(): + return ContainerStateRunning, nil + case ContainerStateStopped.String(): + return ContainerStateStopped, nil + case ContainerStatePaused.String(): + return ContainerStatePaused, nil + case ContainerStateExited.String(): + return ContainerStateExited, nil + default: + return ContainerStateUnknown, errors.Wrapf(ErrInvalidArg, "unknown container state: %s", status) + } +} + // Config accessors // Unlocked diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 1bca99cec..598cde98e 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -304,3 +304,13 @@ func ReadExitFile(runtimeTmp, ctrID string) (int, error) { return exitCode, nil } + +// Ps ... +func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) { + maxWorkers := shared.Parallelize("ps") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers) +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 3730827c7..79177f5ff 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -292,3 +292,75 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode func ReadExitFile(runtimeTmp, ctrID string) (int, error) { return 0, libpod.ErrNotImplemented } + +// Ps ... +func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) { + var psContainers []shared.PsContainerOutput + last := int64(c.Last) + PsOpts := iopodman.PsOpts{ + All: c.All, + Filters: &c.Filter, + Last: &last, + Latest: &c.Latest, + NoTrunc: &c.NoTrunct, + Pod: &c.Pod, + Quiet: &c.Quiet, + Sort: &c.Sort, + Sync: &c.Sync, + } + containers, err := iopodman.Ps().Call(r.Conn, PsOpts) + if err != nil { + return nil, err + } + for _, ctr := range containers { + createdAt, err := time.Parse(time.RFC3339Nano, ctr.CreatedAt) + if err != nil { + return nil, err + } + exitedAt, err := time.Parse(time.RFC3339Nano, ctr.ExitedAt) + if err != nil { + return nil, err + } + startedAt, err := time.Parse(time.RFC3339Nano, ctr.StartedAt) + if err != nil { + return nil, err + } + containerSize := shared.ContainerSize{ + RootFsSize: ctr.RootFsSize, + RwSize: ctr.RwSize, + } + state, err := libpod.StringToContainerStatus(ctr.State) + if err != nil { + return nil, err + } + psc := shared.PsContainerOutput{ + ID: ctr.Id, + Image: ctr.Image, + Command: ctr.Command, + Created: ctr.Created, + Ports: ctr.Ports, + Names: ctr.Names, + IsInfra: ctr.IsInfra, + Status: ctr.Status, + State: state, + Pid: int(ctr.PidNum), + Size: &containerSize, + Pod: ctr.Pod, + CreatedAt: createdAt, + ExitedAt: exitedAt, + StartedAt: startedAt, + Labels: ctr.Labels, + PID: ctr.NsPid, + Cgroup: ctr.Cgroup, + IPC: ctr.Ipc, + MNT: ctr.Mnt, + NET: ctr.Net, + PIDNS: ctr.PidNs, + User: ctr.User, + UTS: ctr.Uts, + Mounts: ctr.Mounts, + } + psContainers = append(psContainers, psc) + } + return psContainers, nil +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index ac1352dac..816a72953 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -47,6 +47,55 @@ func (i *LibpodAPI) ListContainers(call iopodman.VarlinkCall) error { return call.ReplyListContainers(listContainers) } +func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { + var ( + containers []iopodman.PsContainer + ) + maxWorkers := shared.Parallelize("ps") + psOpts := makePsOpts(opts) + filters := []string{} + if opts.Filters != nil { + filters = *opts.Filters + } + psContainerOutputs, err := shared.GetPsContainerOutput(i.Runtime, psOpts, filters, maxWorkers) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + for _, ctr := range psContainerOutputs { + container := iopodman.PsContainer{ + Id: ctr.ID, + Image: ctr.Image, + Command: ctr.Command, + Created: ctr.Created, + Ports: ctr.Ports, + Names: ctr.Names, + IsInfra: ctr.IsInfra, + Status: ctr.Status, + State: ctr.State.String(), + PidNum: int64(ctr.Pid), + RootFsSize: ctr.Size.RootFsSize, + RwSize: ctr.Size.RwSize, + Pod: ctr.Pod, + CreatedAt: ctr.CreatedAt.Format(time.RFC3339Nano), + ExitedAt: ctr.ExitedAt.Format(time.RFC3339Nano), + StartedAt: ctr.StartedAt.Format(time.RFC3339Nano), + Labels: ctr.Labels, + NsPid: ctr.PID, + Cgroup: ctr.Cgroup, + Ipc: ctr.Cgroup, + Mnt: ctr.MNT, + Net: ctr.NET, + PidNs: ctr.PIDNS, + User: ctr.User, + Uts: ctr.UTS, + Mounts: ctr.Mounts, + } + containers = append(containers, container) + } + return call.ReplyPs(containers) +} + // GetContainer ... func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { ctr, err := i.Runtime.LookupContainer(id) diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 3c4b9b79a..8716c963a 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -162,3 +162,36 @@ func stringPullPolicyToType(s string) buildah.PullPolicy { } return buildah.PullIfMissing } + +func derefBool(inBool *bool) bool { + if inBool == nil { + return false + } + return *inBool +} + +func derefString(in *string) string { + if in == nil { + return "" + } + return *in +} + +func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions { + last := 0 + if inOpts.Last != nil { + lastT := *inOpts.Last + last = int(lastT) + } + return shared.PsOptions{ + All: inOpts.All, + Last: last, + Latest: derefBool(inOpts.Latest), + NoTrunc: derefBool(inOpts.NoTrunc), + Pod: derefBool(inOpts.Pod), + Size: true, + Sort: derefString(inOpts.Sort), + Namespace: true, + Sync: derefBool(inOpts.Sync), + } +} -- cgit v1.2.3-54-g00ecf