mirror of
https://github.com/containers/podman.git
synced 2025-05-20 08:36:23 +08:00

Allow automatic generation for shell completion scripts with the internal cobra functions (requires v1.0.0+). This should replace the handwritten completion scripts and even adds support for fish. With this approach it is less likley that completions and code are out of sync. We can now create the scripts with - podman completion bash - podman completion zsh - podman completion fish To test the completion run: source <(podman completion bash) The same works for podman-remote and podman --remote and it will complete your remote containers/images with the correct endpoints values from --url/--connection. The completion logic is written in go and provided by the cobra library. The completion functions lives in `cmd/podman/completion/completion.go`. The unit test at cmd/podman/shell_completion_test.go checks if each command and flag has an autocompletion function set. This prevents that commands and flags have no shell completion set. This commit does not replace the current autocompletion scripts. Closes #6440 Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
276 lines
7.6 KiB
Go
276 lines
7.6 KiB
Go
package containers
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"text/tabwriter"
|
|
"text/template"
|
|
|
|
tm "github.com/buger/goterm"
|
|
"github.com/containers/common/pkg/report"
|
|
"github.com/containers/podman/v2/cmd/podman/common"
|
|
"github.com/containers/podman/v2/cmd/podman/parse"
|
|
"github.com/containers/podman/v2/cmd/podman/registry"
|
|
"github.com/containers/podman/v2/cmd/podman/validate"
|
|
"github.com/containers/podman/v2/libpod/define"
|
|
"github.com/containers/podman/v2/pkg/cgroups"
|
|
"github.com/containers/podman/v2/pkg/domain/entities"
|
|
"github.com/containers/podman/v2/pkg/rootless"
|
|
"github.com/containers/podman/v2/utils"
|
|
"github.com/docker/go-units"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers."
|
|
statsCommand = &cobra.Command{
|
|
Use: "stats [options] [CONTAINER...]",
|
|
Short: "Display a live stream of container resource usage statistics",
|
|
Long: statsDescription,
|
|
RunE: stats,
|
|
Args: checkStatOptions,
|
|
ValidArgsFunction: common.AutocompleteContainersRunning,
|
|
Example: `podman stats --all --no-stream
|
|
podman stats ctrID
|
|
podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
|
}
|
|
|
|
containerStatsCommand = &cobra.Command{
|
|
Use: statsCommand.Use,
|
|
Short: statsCommand.Short,
|
|
Long: statsCommand.Long,
|
|
RunE: statsCommand.RunE,
|
|
Args: checkStatOptions,
|
|
ValidArgsFunction: statsCommand.ValidArgsFunction,
|
|
Example: `podman container stats --all --no-stream
|
|
podman container stats ctrID
|
|
podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`,
|
|
}
|
|
)
|
|
|
|
// statsOptionsCLI is used for storing CLI arguments. Some fields are later
|
|
// used in the backend.
|
|
type statsOptionsCLI struct {
|
|
All bool
|
|
Format string
|
|
Latest bool
|
|
NoReset bool
|
|
NoStream bool
|
|
}
|
|
|
|
var (
|
|
statsOptions statsOptionsCLI
|
|
)
|
|
|
|
func statFlags(cmd *cobra.Command) {
|
|
flags := cmd.Flags()
|
|
|
|
flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false")
|
|
|
|
formatFlagName := "format"
|
|
flags.StringVar(&statsOptions.Format, formatFlagName, "", "Pretty-print container statistics to JSON or using a Go template")
|
|
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteJSONFormat)
|
|
|
|
flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals")
|
|
flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false")
|
|
}
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
|
Command: statsCommand,
|
|
})
|
|
statFlags(statsCommand)
|
|
validate.AddLatestFlag(statsCommand, &statsOptions.Latest)
|
|
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
|
Command: containerStatsCommand,
|
|
Parent: containerCmd,
|
|
})
|
|
statFlags(containerStatsCommand)
|
|
validate.AddLatestFlag(containerStatsCommand, &statsOptions.Latest)
|
|
}
|
|
|
|
// stats is different in that it will assume running containers if
|
|
// no input is given, so we need to validate differently
|
|
func checkStatOptions(cmd *cobra.Command, args []string) error {
|
|
opts := 0
|
|
if statsOptions.All {
|
|
opts++
|
|
}
|
|
if statsOptions.Latest {
|
|
opts++
|
|
}
|
|
if len(args) > 0 {
|
|
opts++
|
|
}
|
|
if opts > 1 {
|
|
return errors.Errorf("--all, --latest and containers cannot be used together")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stats(cmd *cobra.Command, args []string) error {
|
|
if rootless.IsRootless() {
|
|
unified, err := cgroups.IsCgroup2UnifiedMode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !unified {
|
|
return errors.New("stats is not supported in rootless mode without cgroups v2")
|
|
}
|
|
}
|
|
|
|
// Convert to the entities options. We should not leak CLI-only
|
|
// options into the backend and separate concerns.
|
|
opts := entities.ContainerStatsOptions{
|
|
Latest: statsOptions.Latest,
|
|
Stream: !statsOptions.NoStream,
|
|
}
|
|
statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for report := range statsChan {
|
|
if report.Error != nil {
|
|
return report.Error
|
|
}
|
|
if err := outputStats(report.Stats); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func outputStats(reports []define.ContainerStats) error {
|
|
headers := report.Headers(define.ContainerStats{}, map[string]string{
|
|
"ID": "ID",
|
|
"CPUPerc": "CPU %",
|
|
"MemUsage": "MEM USAGE / LIMIT",
|
|
"MemPerc": "MEM %",
|
|
"NetIO": "NET IO",
|
|
"BlockIO": "BLOCK IO",
|
|
"PIDS": "PIDS",
|
|
})
|
|
if !statsOptions.NoReset {
|
|
tm.Clear()
|
|
tm.MoveCursor(1, 1)
|
|
tm.Flush()
|
|
}
|
|
stats := make([]containerStats, 0, len(reports))
|
|
for _, r := range reports {
|
|
stats = append(stats, containerStats{r})
|
|
}
|
|
if report.IsJSON(statsOptions.Format) {
|
|
return outputJSON(stats)
|
|
}
|
|
format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n"
|
|
if len(statsOptions.Format) > 0 {
|
|
format = report.NormalizeFormat(statsOptions.Format)
|
|
}
|
|
format = parse.EnforceRange(format)
|
|
|
|
tmpl, err := template.New("stats").Parse(format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
|
defer w.Flush()
|
|
|
|
if len(statsOptions.Format) < 1 {
|
|
if err := tmpl.Execute(w, headers); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := tmpl.Execute(w, stats); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type containerStats struct {
|
|
define.ContainerStats
|
|
}
|
|
|
|
func (s *containerStats) ID() string {
|
|
return s.ContainerID[0:12]
|
|
}
|
|
|
|
func (s *containerStats) CPUPerc() string {
|
|
return floatToPercentString(s.CPU)
|
|
}
|
|
|
|
func (s *containerStats) MemPerc() string {
|
|
return floatToPercentString(s.ContainerStats.MemPerc)
|
|
}
|
|
|
|
func (s *containerStats) NetIO() string {
|
|
return combineHumanValues(s.NetInput, s.NetOutput)
|
|
}
|
|
|
|
func (s *containerStats) BlockIO() string {
|
|
return combineHumanValues(s.BlockInput, s.BlockOutput)
|
|
}
|
|
|
|
func (s *containerStats) PIDS() string {
|
|
if s.PIDs == 0 {
|
|
// If things go bazinga, return a safe value
|
|
return "--"
|
|
}
|
|
return fmt.Sprintf("%d", s.PIDs)
|
|
}
|
|
func (s *containerStats) MemUsage() string {
|
|
return combineHumanValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit)
|
|
}
|
|
|
|
func floatToPercentString(f float64) string {
|
|
strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
|
|
if err != nil || strippedFloat == 0 {
|
|
// If things go bazinga, return a safe value
|
|
return "--"
|
|
}
|
|
return fmt.Sprintf("%.2f", strippedFloat) + "%"
|
|
}
|
|
|
|
func combineHumanValues(a, b uint64) string {
|
|
if a == 0 && b == 0 {
|
|
return "-- / --"
|
|
}
|
|
return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
|
|
}
|
|
|
|
func outputJSON(stats []containerStats) error {
|
|
type jstat struct {
|
|
Id string `json:"id"` // nolint
|
|
Name string `json:"name"`
|
|
CpuPercent string `json:"cpu_percent"` // nolint
|
|
MemUsage string `json:"mem_usage"`
|
|
MemPerc string `json:"mem_percent"`
|
|
NetIO string `json:"net_io"`
|
|
BlockIO string `json:"block_io"`
|
|
Pids string `json:"pids"`
|
|
}
|
|
jstats := make([]jstat, 0, len(stats))
|
|
for _, j := range stats {
|
|
jstats = append(jstats, jstat{
|
|
Id: j.ID(),
|
|
Name: j.Name,
|
|
CpuPercent: j.CPUPerc(),
|
|
MemUsage: j.MemUsage(),
|
|
MemPerc: j.MemPerc(),
|
|
NetIO: j.NetIO(),
|
|
BlockIO: j.BlockIO(),
|
|
Pids: j.PIDS(),
|
|
})
|
|
}
|
|
b, err := json.MarshalIndent(jstats, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(b))
|
|
return nil
|
|
}
|