podman-remote ps

add the ability to run ps on containers using the remote client.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-04-09 13:07:27 -05:00
parent fe79bdd07e
commit 23cd1928ec
10 changed files with 407 additions and 179 deletions

View File

@ -16,7 +16,6 @@ func getMainCommands() []*cobra.Command {
_execCommand,
_generateCommand,
_playCommand,
&_psCommand,
_loginCommand,
_logoutCommand,
_mountCommand,

View File

@ -52,6 +52,7 @@ var mainCommands = []*cobra.Command{
_loadCommand,
_logsCommand,
podCommand.Command,
&_psCommand,
_pullCommand,
_pushCommand,
&_rmiCommand,

View File

@ -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=(<image-name>[:tag]|<image-id>| ⟨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=(<volume-name>|<mount-point-destination>)
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,10 +424,8 @@ 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
)
opts := shared.PsOptions{
@ -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 {

View File

@ -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=(<image-name>[:tag]|<image-id>| ⟨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=(<volume-name>|<mount-point-destination>)
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 {

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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),
}
}