diff options
Diffstat (limited to 'cmd/podman/pods')
-rw-r--r-- | cmd/podman/pods/create.go | 40 | ||||
-rw-r--r-- | cmd/podman/pods/exists.go | 3 | ||||
-rw-r--r-- | cmd/podman/pods/inspect.go | 3 | ||||
-rw-r--r-- | cmd/podman/pods/pod.go | 5 | ||||
-rw-r--r-- | cmd/podman/pods/ps.go | 103 | ||||
-rw-r--r-- | cmd/podman/pods/stats.go | 189 | ||||
-rw-r--r-- | cmd/podman/pods/stop.go | 3 |
7 files changed, 298 insertions, 48 deletions
diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 63dab4707..ff21166f3 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/common" "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/cmd/podman/registry" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/specgen" @@ -50,8 +49,8 @@ func init() { flags.AddFlagSet(common.GetNetFlags()) flags.StringVar(&createOptions.CGroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") - flags.StringVar(&createOptions.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod") - flags.StringVar(&createOptions.InfraCommand, "infra-command", define.DefaultInfraCommand, "The command to run on the infra container when the pod is started") + flags.StringVar(&createOptions.InfraImage, "infra-image", containerConfig.Engine.InfraImage, "The image of the infra container to associate with the pod") + flags.StringVar(&createOptions.InfraCommand, "infra-command", containerConfig.Engine.InfraCommand, "The command to run on the infra container when the pod is started") flags.StringSliceVar(&labelFile, "label-file", []string{}, "Read in a line delimited file of labels") flags.StringSliceVarP(&labels, "label", "l", []string{}, "Set metadata on pod (default [])") flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") @@ -70,10 +69,24 @@ func create(cmd *cobra.Command, args []string) error { return errors.Wrapf(err, "unable to process labels") } - if !createOptions.Infra && cmd.Flag("share").Changed && share != "none" && share != "" { - return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") + if !createOptions.Infra { + if cmd.Flag("infra-command").Changed { + return errors.New("cannot set infra-command without an infra container") + } + createOptions.InfraCommand = "" + if cmd.Flag("infra-image").Changed { + return errors.New("cannot set infra-image without an infra container") + } + createOptions.InfraImage = "" + + if cmd.Flag("share").Changed && share != "none" && share != "" { + return fmt.Errorf("cannot set share(%s) namespaces without an infra container", cmd.Flag("share").Value) + } + createOptions.Share = nil + } else { + createOptions.Share = strings.Split(share, ",") } - createOptions.Share = strings.Split(share, ",") + if cmd.Flag("pod-id-file").Changed { podIdFile, err = util.OpenExclusiveFile(podIDFile) if err != nil && os.IsExist(err) { @@ -123,21 +136,6 @@ func create(cmd *cobra.Command, args []string) error { } } - if !createOptions.Infra { - if cmd.Flag("infra-command").Changed { - return errors.New("cannot set infra-command without an infra container") - } - createOptions.InfraCommand = "" - if cmd.Flag("infra-image").Changed { - return errors.New("cannot set infra-image without an infra container") - } - createOptions.InfraImage = "" - if cmd.Flag("share").Changed { - return errors.New("cannot set share namespaces without an infra container") - } - createOptions.Share = nil - } - response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions) if err != nil { return err diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go index ad0e28b90..5a94bf150 100644 --- a/cmd/podman/pods/exists.go +++ b/cmd/podman/pods/exists.go @@ -2,7 +2,6 @@ package pods import ( "context" - "os" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -37,7 +36,7 @@ func exists(cmd *cobra.Command, args []string) error { return err } if !response.Value { - os.Exit(1) + registry.SetExitCode(1) } return nil } diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index 901ae50b2..1e333247b 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -6,7 +6,6 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" - jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -55,7 +54,7 @@ func inspect(cmd *cobra.Command, args []string) error { if err != nil { return err } - b, err := jsoniter.MarshalIndent(responses, "", " ") + b, err := json.MarshalIndent(responses, "", " ") if err != nil { return err } diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index 1cac50e40..e86b8aba4 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -3,10 +3,14 @@ package pods import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" "github.com/spf13/cobra" ) var ( + // Pull in configured json library + json = registry.JsonLibrary() + // Command: podman _pod_ podCmd = &cobra.Command{ Use: "pod", @@ -15,6 +19,7 @@ var ( TraverseChildren: true, RunE: registry.SubCommandExists, } + containerConfig = util.DefaultContainerConfig() ) func init() { diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 8cb7b6266..6d0d9cf7f 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -2,19 +2,18 @@ package pods import ( "context" - "encoding/json" "fmt" "io" "os" + "sort" "strings" "text/tabwriter" "text/template" "time" - "github.com/docker/go-units" - "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -34,7 +33,7 @@ var ( var ( defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED" - inputFilters string + inputFilters []string noTrunc bool psInput entities.PodPSOptions ) @@ -50,7 +49,7 @@ func init() { flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status") // TODO should we make this a [] ? - flags.StringVarP(&inputFilters, "filter", "f", "", "Filter output based on conditions given") + flags.StringSliceVarP(&inputFilters, "filter", "f", []string{}, "Filter output based on conditions given") flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template") flags.BoolVarP(&psInput.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod") @@ -69,8 +68,13 @@ func pods(cmd *cobra.Command, args []string) error { row string lpr []ListPodReporter ) + + if psInput.Quiet && len(psInput.Format) > 0 { + return errors.New("quiet and format cannot be used together") + } if cmd.Flag("filter").Changed { - for _, f := range strings.Split(inputFilters, ",") { + psInput.Filters = make(map[string][]string) + for _, f := range inputFilters { split := strings.Split(f, "=") if len(split) < 2 { return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) @@ -83,6 +87,10 @@ func pods(cmd *cobra.Command, args []string) error { return err } + if err := sortPodPsOutput(psInput.Sort, responses); err != nil { + return err + } + if psInput.Format == "json" { b, err := json.MarshalIndent(responses, "", " ") if err != nil { @@ -97,11 +105,7 @@ func pods(cmd *cobra.Command, args []string) error { } headers, row := createPodPsOut() if psInput.Quiet { - if noTrunc { - row = "{{.Id}}\n" - } else { - row = "{{slice .Id 0 12}}\n" - } + row = "{{.Id}}\n" } if cmd.Flag("format").Changed { row = psInput.Format @@ -132,11 +136,7 @@ func pods(cmd *cobra.Command, args []string) error { func createPodPsOut() (string, string) { var row string headers := defaultHeaders - if noTrunc { - row += "{{.Id}}" - } else { - row += "{{slice .Id 0 12}}" - } + row += "{{.Id}}" row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}" @@ -162,11 +162,7 @@ func createPodPsOut() (string, string) { } headers += "\tINFRA ID\n" - if noTrunc { - row += "\t{{.InfraId}}\n" - } else { - row += "\t{{slice .InfraId 0 12}}\n" - } + row += "\t{{.InfraId}}\n" return headers, row } @@ -186,6 +182,19 @@ func (l ListPodReporter) NumberOfContainers() int { return len(l.Containers) } +// ID is a wrapper to Id for compat, typos +func (l ListPodReporter) ID() string { + return l.Id() +} + +// Id returns the Pod id +func (l ListPodReporter) Id() string { + if noTrunc { + return l.ListPodsReport.Id + } + return l.ListPodsReport.Id[0:12] +} + // Added for backwards compatibility with podmanv1 func (l ListPodReporter) InfraID() string { return l.InfraId() @@ -194,6 +203,9 @@ func (l ListPodReporter) InfraID() string { // InfraId returns the infra container id for the pod // depending on trunc func (l ListPodReporter) InfraId() string { + if len(l.ListPodsReport.InfraId) == 0 { + return "" + } if noTrunc { return l.ListPodsReport.InfraId } @@ -227,3 +239,52 @@ func (l ListPodReporter) ContainerStatuses() string { } return strings.Join(statuses, ",") } + +func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error { + switch sortBy { + case "created": + sort.Sort(podPsSortedCreated{lprs}) + case "id": + sort.Sort(podPsSortedId{lprs}) + case "name": + sort.Sort(podPsSortedName{lprs}) + case "number": + sort.Sort(podPsSortedNumber{lprs}) + case "status": + sort.Sort(podPsSortedStatus{lprs}) + default: + return errors.Errorf("invalid option for --sort, options are: id, names, or number") + } + return nil +} + +type lprSort []*entities.ListPodsReport + +func (a lprSort) Len() int { return len(a) } +func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type podPsSortedCreated struct{ lprSort } + +func (a podPsSortedCreated) Less(i, j int) bool { + return a.lprSort[i].Created.After(a.lprSort[j].Created) +} + +type podPsSortedId struct{ lprSort } + +func (a podPsSortedId) Less(i, j int) bool { return a.lprSort[i].Id < a.lprSort[j].Id } + +type podPsSortedNumber struct{ lprSort } + +func (a podPsSortedNumber) Less(i, j int) bool { + return len(a.lprSort[i].Containers) < len(a.lprSort[j].Containers) +} + +type podPsSortedName struct{ lprSort } + +func (a podPsSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name } + +type podPsSortedStatus struct{ lprSort } + +func (a podPsSortedStatus) Less(i, j int) bool { + return a.lprSort[i].Status < a.lprSort[j].Status +} diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go new file mode 100644 index 000000000..7c3597d9a --- /dev/null +++ b/cmd/podman/pods/stats.go @@ -0,0 +1,189 @@ +package pods + +import ( + "context" + "fmt" + "os" + "reflect" + "strings" + "text/tabwriter" + "text/template" + "time" + + "github.com/buger/goterm" + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util/camelcase" + "github.com/spf13/cobra" +) + +type podStatsOptionsWrapper struct { + entities.PodStatsOptions + + // Format - pretty-print to JSON or a go template. + Format string + // NoReset - do not reset the screen when streaming. + NoReset bool + // NoStream - do not stream stats but write them once. + NoStream bool +} + +var ( + statsOptions = podStatsOptionsWrapper{} + statsDescription = `Display the containers' resource-usage statistics of one or more running pod` + // Command: podman pod _pod_ + statsCmd = &cobra.Command{ + Use: "stats [flags] [POD...]", + Short: "Display resource-usage statistics of pods", + Long: statsDescription, + RunE: stats, + Example: `podman pod stats + podman pod stats a69b23034235 named-pod + podman pod stats --latest + podman pod stats --all`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: statsCmd, + Parent: podCmd, + }) + + flags := statsCmd.Flags() + flags.BoolVarP(&statsOptions.All, "all", "a", false, "Provide stats for all pods") + flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Provide stats on the latest pod Podman is aware of") + flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen when streaming") + flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result") + + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func stats(cmd *cobra.Command, args []string) error { + // Validate input. + if err := entities.ValidatePodStatsOptions(args, &statsOptions.PodStatsOptions); err != nil { + return err + } + + format := statsOptions.Format + doJson := strings.ToLower(format) == formats.JSONString + header := getPodStatsHeader(format) + + for { + reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions) + if err != nil { + return err + } + // Print the stats in the requested format and configuration. + if doJson { + if err := printJSONPodStats(reports); err != nil { + return err + } + } else { + if !statsOptions.NoReset { + goterm.Clear() + goterm.MoveCursor(1, 1) + goterm.Flush() + } + if len(format) == 0 { + printPodStatsLines(reports) + } else if err := printFormattedPodStatsLines(format, reports, header); err != nil { + return err + } + } + if statsOptions.NoStream { + break + } + time.Sleep(time.Second) + } + + return nil +} + +func printJSONPodStats(stats []*entities.PodStatsReport) error { + b, err := json.MarshalIndent(&stats, "", " ") + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "%s\n", string(b)) + return nil +} + +func printPodStatsLines(stats []*entities.PodStatsReport) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" + fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") + for _, i := range stats { + if len(stats) == 0 { + fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--") + } else { + fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS) + } + } + w.Flush() +} + +func printFormattedPodStatsLines(format string, stats []*entities.PodStatsReport, headerNames map[string]string) error { + if len(stats) == 0 { + return nil + } + + // Use a tabwriter to align column format + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + // Spit out the header if "table" is present in the format + if strings.HasPrefix(format, "table") { + hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) + format = hformat + headerTmpl, err := template.New("header").Parse(hformat) + if err != nil { + return err + } + if err := headerTmpl.Execute(w, headerNames); err != nil { + return err + } + fmt.Fprintln(w, "") + } + + // Spit out the data rows now + dataTmpl, err := template.New("data").Parse(format) + if err != nil { + return err + } + for _, s := range stats { + if err := dataTmpl.Execute(w, s); err != nil { + return err + } + fmt.Fprintln(w, "") + } + // Flush the writer + return w.Flush() + +} + +// getPodStatsHeader returns the stats header for the specified options. +func getPodStatsHeader(format string) map[string]string { + headerNames := make(map[string]string) + if format == "" { + return headerNames + } + // Make a map of the field names for the headers + v := reflect.ValueOf(entities.PodStatsReport{}) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + split := camelcase.Split(t.Field(i).Name) + value := strings.ToUpper(strings.Join(split, " ")) + switch value { + case "CPU", "MEM": + value += " %" + case "MEM USAGE": + value = "MEM USAGE / LIMIT" + } + headerNames[t.Field(i).Name] = value + } + return headerNames +} diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go index 683d9c00a..daf05d640 100644 --- a/cmd/podman/pods/stop.go +++ b/cmd/podman/pods/stop.go @@ -47,11 +47,10 @@ func init() { flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running pods") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing") flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") - flags.UintVarP(&timeout, "time", "t", 0, "Seconds to wait for pod stop before killing the container") + flags.UintVarP(&timeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container") if registry.IsRemote() { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("ignore") - } flags.SetNormalizeFunc(utils.AliasFlags) } |