diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/pod.go | 3 | ||||
-rw-r--r-- | cmd/podman/pod_create.go | 18 | ||||
-rw-r--r-- | cmd/podman/pod_ps.go | 431 | ||||
-rw-r--r-- | cmd/podman/pod_rm.go | 26 |
4 files changed, 429 insertions, 49 deletions
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index f32ae4626..6cf2920a5 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -8,7 +8,8 @@ var ( podDescription = ` podman pod - manage pods + Manage container pods. + Pods are a group of one or more containers sharing the same network, pid and ipc namespaces. ` podCommand = cli.Command{ Name: "pod", diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 9bbc60d4d..f86faa409 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -17,13 +17,9 @@ var podCreateDescription = "Creates a new empty pod. The pod ID is then" + " initial state 'created'." var podCreateFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "cgroup-to-ctr", - Usage: "Tells containers in this pod to use the cgroup created for the pod", - }, cli.StringFlag{ Name: "cgroup-parent", - Usage: "Optional parent cgroup for the pod", + Usage: "Set parent cgroup for the pod", }, cli.StringSliceFlag{ Name: "label-file", @@ -45,7 +41,7 @@ var podCreateFlags = []cli.Flag{ var podCreateCommand = cli.Command{ Name: "create", - Usage: "create but do not start a pod", + Usage: "create a new empty pod", Description: podCreateDescription, Flags: podCreateFlags, Action: podCreateCmd, @@ -75,18 +71,11 @@ func podCreateCmd(c *cli.Context) error { return errors.Wrapf(err, "unable to write pod id file %s", c.String("pod-id-file")) } } - // BEGIN GetPodCreateOptions - // TODO make sure this is correct usage if c.IsSet("cgroup-parent") { options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent"))) } - if c.Bool("cgroup-to-ctr") { - options = append(options, libpod.WithPodCgroups()) - } - // LABEL VARIABLES - // TODO make sure this works as expected labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) if err != nil { return errors.Wrapf(err, "unable to process labels") @@ -99,6 +88,9 @@ func podCreateCmd(c *cli.Context) error { options = append(options, libpod.WithPodName(c.String("name"))) } + // always have containers use pod cgroups + options = append(options, libpod.WithPodCgroups()) + pod, err := runtime.NewPod(options...) if err != nil { return err diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index f4e2d0ae5..470810901 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -2,31 +2,62 @@ package main import ( "reflect" + "sort" + "strconv" "strings" + "time" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/batchcontainer" "github.com/projectatomic/libpod/cmd/podman/formats" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/libpod" + "github.com/projectatomic/libpod/pkg/util" "github.com/urfave/cli" ) +const ( + STOPPED = "Stopped" + RUNNING = "Running" + PAUSED = "Paused" + EXITED = "Exited" + ERROR = "Error" + CREATED = "Created" + NUM_CTR_INFO = 10 +) + var ( - opts batchcontainer.PsOptions + bc_opts batchcontainer.PsOptions ) +type podPsCtrInfo struct { + Name string `"json:name,omitempty"` + Id string `"json:id,omitempty"` + Status string `"json:status,omitempty"` +} + type podPsOptions struct { NoTrunc bool Format string + Sort string Quiet bool NumberOfContainers bool + Cgroup bool + NamesOfContainers bool + IdsOfContainers bool + StatusOfContainers bool } type podPsTemplateParams struct { + Created string ID string Name string NumberOfContainers int + Status string + Cgroup string + UsePodCgroup bool + ContainerInfo string } // podPsJSONParams is used as a base structure for the psParams @@ -35,23 +66,93 @@ type podPsTemplateParams struct { // podPsJSONParams will be populated by data from libpod.Container, // the members of the struct are the sama data types as their sources. type podPsJSONParams struct { - ID string `json:"id"` - Name string `json:"name"` - NumberOfContainers int `json:"numberofcontainers"` + CreatedAt time.Time `json:"createdAt"` + ID string `json:"id"` + Name string `json:"name"` + NumberOfContainers int `json:"numberofcontainers"` + Status string `json:"status"` + CtrsInfo []podPsCtrInfo `json:"containerinfo,omitempty"` + Cgroup string `json:"cgroup,omitempty"` + UsePodCgroup bool `json:"podcgroup,omitempty"` +} + +// Type declaration and functions for sorting the pod PS output +type podPsSorted []podPsJSONParams + +func (a podPsSorted) Len() int { return len(a) } +func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type podPsSortedCreated struct{ podPsSorted } + +func (a podPsSortedCreated) Less(i, j int) bool { + return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt) +} + +type podPsSortedId struct{ podPsSorted } + +func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID } + +type podPsSortedNumber struct{ podPsSorted } + +func (a podPsSortedNumber) Less(i, j int) bool { + return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo) +} + +type podPsSortedName struct{ podPsSorted } + +func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name } + +type podPsSortedStatus struct{ podPsSorted } + +func (a podPsSortedStatus) Less(i, j int) bool { + return a.podPsSorted[i].Status < a.podPsSorted[j].Status } var ( podPsFlags = []cli.Flag{ cli.BoolFlag{ + Name: "cgroup", + Usage: "Print the Cgroup information of the pod", + }, + cli.BoolFlag{ + Name: "ctr-names", + Usage: "Display the container names", + }, + cli.BoolFlag{ + Name: "ctr-ids", + Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated", + }, + cli.BoolFlag{ + Name: "ctr-status", + Usage: "Display the container status", + }, + cli.StringFlag{ + Name: "filter, f", + Usage: "Filter output based on conditions given", + }, + cli.StringFlag{ + Name: "format", + Usage: "Pretty-print pods to JSON or using a Go template", + }, + cli.BoolFlag{ + Name: "latest, l", + Usage: "Show the latest pod created", + }, + cli.BoolFlag{ Name: "no-trunc", - Usage: "Display the extended information", + Usage: "Do not truncate pod and container IDs", }, cli.BoolFlag{ Name: "quiet, q", Usage: "Print the numeric IDs of the pods only", }, + cli.StringFlag{ + Name: "sort", + Usage: "Sort output by created, id, name, or number", + Value: "created", + }, } - podPsDescription = "Prints out information about pods" + podPsDescription = "List all pods on system including their names, ids and current state." podPsCommand = cli.Command{ Name: "ps", Aliases: []string{"ls", "list"}, @@ -68,7 +169,7 @@ func podPsCmd(c *cli.Context) error { return err } - if err := podCheckFlagsPassed(c); err != nil { + if err := podPsCheckFlagsPassed(c); err != nil { return errors.Wrapf(err, "error with flags passed") } @@ -76,50 +177,194 @@ func podPsCmd(c *cli.Context) error { if err != nil { 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") } - format := genPodPsFormat(c.Bool("quiet")) - opts := podPsOptions{ - Format: format, - NoTrunc: c.Bool("no-trunc"), - Quiet: c.Bool("quiet"), + NoTrunc: c.Bool("no-trunc"), + Quiet: c.Bool("quiet"), + Sort: c.String("sort"), + IdsOfContainers: c.Bool("ctr-ids"), + NamesOfContainers: c.Bool("ctr-names"), + StatusOfContainers: c.Bool("ctr-status"), } + opts.Format = genPodPsFormat(c) + var filterFuncs []libpod.PodFilter + if c.String("filter") != "" { + filters := strings.Split(c.String("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 := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime) + if err != nil { + return errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) + } + } - pods, err := runtime.Pods(filterFuncs...) - if err != nil { - return err + var pods []*libpod.Pod + if c.IsSet("latest") { + pod, err := runtime.GetLatestPod() + if err != nil { + return err + } + pods = append(pods, pod) + } else { + pods, err = runtime.GetAllPods() + if err != nil { + return err + } + } + + podsFiltered := make([]*libpod.Pod, 0, len(pods)) + for _, pod := range pods { + include := true + for _, filter := range filterFuncs { + include = include && filter(pod) + } + + if include { + podsFiltered = append(podsFiltered, pod) + } } - return generatePodPsOutput(pods, opts, runtime) + return generatePodPsOutput(podsFiltered, opts, runtime) } -// podCheckFlagsPassed checks if mutually exclusive flags are passed together -func podCheckFlagsPassed(c *cli.Context) error { +// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together +func podPsCheckFlagsPassed(c *cli.Context) error { // quiet, and format with Go template are mutually exclusive flags := 0 if c.Bool("quiet") { flags++ } + if c.IsSet("format") && c.String("format") != formats.JSONString { + flags++ + } if flags > 1 { - return errors.Errorf("quiet, and format with Go template are mutually exclusive") + return errors.Errorf("quiet and format with Go template are mutually exclusive") } return nil } +func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) { + switch filter { + case "ctr-ids": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + return util.StringInSlice(filterValue, ctrIds) + }, nil + case "ctr-names": + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + for _, ctr := range ctrs { + if filterValue == ctr.Name() { + return true + } + } + return false + }, nil + case "ctr-number": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + + fVint, err2 := strconv.Atoi(filterValue) + if err2 != nil { + return false + } + return len(ctrIds) == fVint + }, nil + case "ctr-status": + if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + for _, ctr := range ctrs { + status, err := ctr.State() + if err != nil { + return false + } + state := status.String() + if status == libpod.ContainerStateConfigured { + state = "created" + } + if state == filterValue { + return true + } + } + return false + }, nil + case "id": + return func(p *libpod.Pod) bool { + return strings.Contains(p.ID(), filterValue) + }, nil + case "name": + return func(p *libpod.Pod) bool { + return strings.Contains(p.Name(), filterValue) + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { + return nil, errors.Errorf("%s is not a valid pod status", filterValue) + } + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + status, err := getPodStatus(ctrs) + if err != nil { + return false + } + if strings.ToLower(status) == filterValue { + return true + } + return false + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + // generate the template based on conditions given -func genPodPsFormat(quiet bool) string { - if quiet { - return formats.IDString +func genPodPsFormat(c *cli.Context) string { + format := "" + if c.String("format") != "" { + // "\t" from the command line is not being recognized as a tab + // replacing the string "\t" to a tab character if the user passes in "\t" + format = strings.Replace(c.String("format"), `\t`, "\t", -1) + } else if c.Bool("quiet") { + format = formats.IDString + } else { + format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" + if c.Bool("cgroup") { + format += "\t{{.Cgroup}}\t{{.UsePodCgroup}}" + } + if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") { + format += "\t{{.ContainerInfo}}" + } else { + format += "\t{{.NumberOfContainers}}" + } } - format := "table {{.ID}}\t{{.Name}}" return format } @@ -152,6 +397,24 @@ func (p *podPsTemplateParams) podHeaderMap() map[string]string { return values } +func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) { + switch sortBy { + case "created": + sort.Sort(podPsSortedCreated{psOutput}) + case "id": + sort.Sort(podPsSortedId{psOutput}) + case "name": + sort.Sort(podPsSortedName{psOutput}) + case "number": + sort.Sort(podPsSortedNumber{psOutput}) + case "status": + sort.Sort(podPsSortedStatus{psOutput}) + default: + return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number") + } + return psOutput, nil +} + // getPodTemplateOutput returns the modified container information func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) { var ( @@ -160,13 +423,43 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP for _, psParam := range psParams { podID := psParam.ID + var ctrStr string + truncated := "" if !opts.NoTrunc { - podID = shortID(psParam.ID) + podID = shortID(podID) + if len(psParam.CtrsInfo) > NUM_CTR_INFO { + psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO] + truncated = "..." + } } + for _, ctrInfo := range psParam.CtrsInfo { + ctrStr += "[ " + if opts.IdsOfContainers { + if opts.NoTrunc { + ctrStr += ctrInfo.Id + } else { + ctrStr += shortID(ctrInfo.Id) + } + } + if opts.NamesOfContainers { + ctrStr += ctrInfo.Name + " " + } + if opts.StatusOfContainers { + ctrStr += ctrInfo.Status + " " + } + ctrStr += "] " + } + ctrStr += truncated params := podPsTemplateParams{ - ID: podID, - Name: psParam.Name, + Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago", + ID: podID, + Name: psParam.Name, + Status: psParam.Status, + NumberOfContainers: psParam.NumberOfContainers, + UsePodCgroup: psParam.UsePodCgroup, + Cgroup: psParam.Cgroup, + ContainerInfo: ctrStr, } psOutput = append(psOutput, params) @@ -175,6 +468,52 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP return psOutput, nil } +func getPodStatus(ctrs []*libpod.Container) (string, error) { + ctrNum := len(ctrs) + if ctrNum == 0 { + return CREATED, nil + } + statuses := map[string]int{ + STOPPED: 0, + RUNNING: 0, + PAUSED: 0, + CREATED: 0, + ERROR: 0, + } + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + return "", err + } + switch state { + case libpod.ContainerStateStopped: + statuses[STOPPED]++ + case libpod.ContainerStateRunning: + statuses[RUNNING]++ + case libpod.ContainerStatePaused: + statuses[PAUSED]++ + case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: + statuses[CREATED]++ + default: + statuses[ERROR]++ + } + } + + if statuses[RUNNING] > 0 { + return RUNNING, nil + } else if statuses[PAUSED] == ctrNum { + return PAUSED, nil + } else if statuses[STOPPED] == ctrNum { + return EXITED, nil + } else if statuses[STOPPED] > 0 { + return STOPPED, nil + } else if statuses[ERROR] > 0 { + return ERROR, nil + } else { + return CREATED, nil + } +} + // getAndSortPodJSONOutput returns the container info in its raw, sorted form func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) { var ( @@ -182,21 +521,55 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib ) for _, pod := range pods { - ctrs, err := runtime.ContainersInPod(pod) + ctrs, err := pod.AllContainers() + ctrsInfo := make([]podPsCtrInfo, 0) if err != nil { return nil, err } ctrNum := len(ctrs) + status, err := getPodStatus(ctrs) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + batchInfo, err := batchcontainer.BatchContainerOp(ctr, bc_opts) + if err != nil { + return nil, err + } + var status string + switch batchInfo.ConState { + case libpod.ContainerStateStopped: + status = EXITED + case libpod.ContainerStateRunning: + status = RUNNING + case libpod.ContainerStatePaused: + status = PAUSED + case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: + status = CREATED + default: + status = ERROR + } + ctrsInfo = append(ctrsInfo, podPsCtrInfo{ + Name: batchInfo.ConConfig.Name, + Id: ctr.ID(), + Status: status, + }) + } params := podPsJSONParams{ + CreatedAt: pod.CreatedTime(), ID: pod.ID(), Name: pod.Name(), + Status: status, + Cgroup: pod.CgroupParent(), + UsePodCgroup: pod.UsePodCgroup(), NumberOfContainers: ctrNum, + CtrsInfo: ctrsInfo, } psOutput = append(psOutput, params) } - return psOutput, nil + return sortPodPsOutput(opts.Sort, psOutput) } func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error { diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index db2b1ee51..8cc46761e 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -20,17 +20,18 @@ var ( Name: "all, a", Usage: "Remove all pods", }, + LatestFlag, } podRmDescription = "Remove one or more pods" podRmCommand = cli.Command{ Name: "rm", Usage: fmt.Sprintf(`podman rm will remove one or more pods from the host. The pod name or ID can be used. - A pod with running or attached containrs will not be removed. - If --force, -f is specified, all containers will be stopped, then removed.`), + A pod with containers will not be removed without --force. + If --force is specified, all containers will be stopped, then removed.`), Description: podRmDescription, Flags: podRmFlags, Action: podRmCmd, - ArgsUsage: "", + ArgsUsage: "[POD ...]", UseShortOptionHandling: true, } ) @@ -42,6 +43,10 @@ func podRmCmd(c *cli.Context) error { return err } + if c.Bool("latest") && c.Bool("all") { + return errors.Errorf("--all and --latest cannot be used together") + } + runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") @@ -50,22 +55,31 @@ func podRmCmd(c *cli.Context) error { args := c.Args() - if len(args) == 0 && !c.Bool("all") { + if len(args) == 0 && !c.Bool("all") && !c.Bool("latest") { return errors.Errorf("specify one or more pods to remove") } var delPods []*libpod.Pod var lastError error - if c.Bool("all") || c.Bool("a") { - delPods, err = runtime.Pods() + if c.IsSet("all") { + delPods, err = runtime.GetAllPods() if err != nil { return errors.Wrapf(err, "unable to get pod list") } + } else if c.IsSet("latest") { + delPod, err := runtime.GetLatestPod() + if err != nil { + return errors.Wrapf(err, "unable to get latest pod") + } + delPods = append(delPods, delPod) } else { for _, i := range args { pod, err := runtime.LookupPod(i) if err != nil { fmt.Fprintln(os.Stderr, err) + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } lastError = errors.Wrapf(err, "unable to find pods %s", i) continue } |