diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/common/util.go | 2 | ||||
-rw-r--r-- | cmd/podman/containers/mount.go | 21 | ||||
-rw-r--r-- | cmd/podman/containers/ps.go | 53 | ||||
-rw-r--r-- | cmd/podman/containers/rm.go | 13 | ||||
-rw-r--r-- | cmd/podman/generate/systemd.go | 7 | ||||
-rw-r--r-- | cmd/podman/images/diff.go | 9 | ||||
-rw-r--r-- | cmd/podman/images/history.go | 57 | ||||
-rw-r--r-- | cmd/podman/images/mount.go | 27 | ||||
-rw-r--r-- | cmd/podman/images/search.go | 61 | ||||
-rw-r--r-- | cmd/podman/inspect/inspect.go | 49 | ||||
-rw-r--r-- | cmd/podman/networks/inspect.go | 38 | ||||
-rw-r--r-- | cmd/podman/play/kube.go | 1 | ||||
-rw-r--r-- | cmd/podman/pods/inspect.go | 24 | ||||
-rw-r--r-- | cmd/podman/pods/ps.go | 108 | ||||
-rw-r--r-- | cmd/podman/pods/stats.go | 94 | ||||
-rw-r--r-- | cmd/podman/root.go | 19 | ||||
-rw-r--r-- | cmd/podman/root_test.go | 34 | ||||
-rw-r--r-- | cmd/podman/volumes/list.go | 54 |
18 files changed, 360 insertions, 311 deletions
diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index 17e779c86..a971aa957 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -200,8 +200,6 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) } newPort.HostPort = hostStart } - } else { - newPort.HostPort = newPort.ContainerPort } hport := newPort.HostPort diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index f2df5e99e..c4dfb513f 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -6,6 +6,7 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -75,9 +76,6 @@ func init() { } func mount(_ *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) if len(args) > 0 && mountOpts.Latest { return errors.Errorf("--latest and containers cannot be used together") } @@ -85,7 +83,9 @@ func mount(_ *cobra.Command, args []string) error { if err != nil { return err } + if len(args) > 0 || mountOpts.Latest || mountOpts.All { + var errs utils.OutputErrors for _, r := range reports { if r.Err == nil { fmt.Println(r.Path) @@ -96,21 +96,21 @@ func mount(_ *cobra.Command, args []string) error { return errs.PrintErrors() } - switch mountOpts.Format { - case "json": + switch { + case parse.MatchesJSONFormat(mountOpts.Format): return printJSON(reports) - case "": - // do nothing + case mountOpts.Format == "": + break // print defaults default: - return errors.Errorf("unknown --format argument: %s", mountOpts.Format) + return errors.Errorf("unknown --format argument: %q", mountOpts.Format) } mrs := make([]mountReporter, 0, len(reports)) for _, r := range reports { mrs = append(mrs, mountReporter{r}) } - row := "{{.ID}} {{.Path}}\n" - format := "{{range . }}" + row + "{{end}}" + + format := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}" tmpl, err := template.New("mounts").Parse(format) if err != nil { return err @@ -139,6 +139,7 @@ func printJSON(reports []*entities.ContainerMountReport) error { if err != nil { return err } + fmt.Println(string(b)) return nil } diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index c4c8b60f3..8082a74c2 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -12,7 +12,9 @@ import ( tm "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -176,47 +178,51 @@ func ps(cmd *cobra.Command, args []string) error { return err } } - if listOpts.Format == "json" { + + switch { + case parse.MatchesJSONFormat(listOpts.Format): return jsonOut(listContainers) - } - if listOpts.Quiet { + case listOpts.Quiet: return quietOut(listContainers) } + // Output table Watch > 0 will refresh screen responses := make([]psReporter, 0, len(listContainers)) for _, r := range listContainers { responses = append(responses, psReporter{r}) } - headers, format := createPsOut() - if cmd.Flag("format").Changed { - format = strings.TrimPrefix(listOpts.Format, "table ") - if !strings.HasPrefix(format, "\n") { - format += "\n" - } - } - format = "{{range . }}" + format + "{{end}}" - if !listOpts.Quiet && !cmd.Flag("format").Changed { - format = headers + format + var headers, format string + if cmd.Flags().Changed("format") { + headers = "" + format = report.NormalizeFormat(listOpts.Format) + } else { + headers, format = createPsOut() } + format = headers + "{{range . }}" + format + "{{end}}" + tmpl, err := template.New("listContainers").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + if listOpts.Watch > 0 { for { var responses []psReporter tm.Clear() tm.MoveCursor(1, 1) tm.Flush() - listContainers, err := getResponses() - for _, r := range listContainers { - responses = append(responses, psReporter{r}) - } - if err != nil { + + if ctnrs, err := getResponses(); err != nil { return err + } else { + for _, r := range ctnrs { + responses = append(responses, psReporter{r}) + } } + if err := tmpl.Execute(w, responses); err != nil { return err } @@ -232,11 +238,11 @@ func ps(cmd *cobra.Command, args []string) error { if err := tmpl.Execute(w, responses); err != nil { return err } - return w.Flush() } return nil } +// cannot use report.Headers() as it doesn't support structures as fields func createPsOut() (string, string) { var row string if listOpts.Namespace { @@ -257,12 +263,9 @@ func createPsOut() (string, string) { headers += "\tSIZE" row += "\t{{.Size}}" } - if !strings.HasSuffix(headers, "\n") { - headers += "\n" - } - if !strings.HasSuffix(row, "\n") { - row += "\n" - } + + headers = report.NormalizeFormat(headers) + row = report.NormalizeFormat(row) return headers, row } diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index f8f12234d..a7739b3ba 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -57,13 +57,12 @@ func rmFlags(flags *pflag.FlagSet) { flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") - flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library") flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") - if registry.IsRemote() { - _ = flags.MarkHidden("ignore") - _ = flags.MarkHidden("cidfile") + if !registry.IsRemote() { + // This option is deprecated, but needs to still exists for backwards compatibility + flags.Bool("storage", false, "Remove container from storage library") _ = flags.MarkHidden("storage") } } @@ -97,12 +96,6 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit var ( errs utils.OutputErrors ) - // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore - if rmOptions.Storage { - if rmOptions.All || rmOptions.Ignore || rmOptions.Latest || rmOptions.Volumes || rmOptions.CIDFiles != nil { - return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile") - } - } responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions) if err != nil { if setExit { diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index f690836a4..02e826549 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -97,10 +98,10 @@ func systemd(cmd *cobra.Command, args []string) error { } } - switch format { - case "json": + switch { + case parse.MatchesJSONFormat(format): return printJSON(report.Units) - case "": + case format == "": return printDefault(report.Units) default: return errors.Errorf("unknown --format argument: %s", format) diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 26147345e..05a05fa04 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -1,6 +1,7 @@ package images import ( + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/pkg/domain/entities" @@ -49,11 +50,11 @@ func diff(cmd *cobra.Command, args []string) error { return err } - switch diffOpts.Format { - case "": - return report.ChangesToTable(results) - case "json": + switch { + case parse.MatchesJSONFormat(diffOpts.Format): return report.ChangesToJSON(results) + case diffOpts.Format == "": + return report.ChangesToTable(results) default: return errors.New("only supported value for '--format' is 'json'") } diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index 30abf0ada..fa4b368c6 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -10,7 +10,9 @@ import ( "time" "unicode" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" @@ -28,9 +30,9 @@ var ( Use: "history [flags] IMAGE", Short: "Show history of a specified image", Long: long, - Example: "podman history quay.io/fedora/fedora", Args: cobra.ExactArgs(1), RunE: history, + Example: "podman history quay.io/fedora/fedora", } imageHistoryCmd = &cobra.Command{ @@ -39,7 +41,7 @@ var ( Short: historyCmd.Short, Long: historyCmd.Long, RunE: historyCmd.RunE, - Example: `podman image history imageID`, + Example: `podman image history quay.io/fedora/fedora`, } opts = struct { @@ -79,7 +81,7 @@ func history(cmd *cobra.Command, args []string) error { return err } - if opts.format == "json" { + if parse.MatchesJSONFormat(opts.format) { var err error if len(results.Layers) == 0 { _, err = fmt.Fprintf(os.Stdout, "[]\n") @@ -100,69 +102,66 @@ func history(cmd *cobra.Command, args []string) error { } return err } - hr := make([]historyreporter, 0, len(results.Layers)) + + hr := make([]historyReporter, 0, len(results.Layers)) for _, l := range results.Layers { - hr = append(hr, historyreporter{l}) + hr = append(hr, historyReporter{l}) } + + hdrs := report.Headers(historyReporter{}, map[string]string{ + "CreatedBy": "CREATED BY", + }) + // Defaults - hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - switch { - case len(opts.format) > 0: - hdr = "" - row = opts.format - if !strings.HasSuffix(opts.format, "\n") { - row += "\n" - } + case cmd.Flags().Changed("format"): + row = report.NormalizeFormat(opts.format) case opts.quiet: - hdr = "" row = "{{.ID}}\n" - case opts.human: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" - case opts.noTrunc: - row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" } - format := hdr + "{{range . }}" + row + "{{end}}" + format := "{{range . }}" + row + "{{end}}" tmpl, err := template.New("report").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - err = tmpl.Execute(w, hr) - if err != nil { - fmt.Fprintln(os.Stderr, errors.Wrapf(err, "failed to print report")) + defer w.Flush() + + if !opts.quiet && !cmd.Flags().Changed("format") { + if err := tmpl.Execute(w, hdrs); err != nil { + return errors.Wrapf(err, "failed to write report column headers") + } } - w.Flush() - return nil + return tmpl.Execute(w, hr) } -type historyreporter struct { +type historyReporter struct { entities.ImageHistoryLayer } -func (h historyreporter) Created() string { +func (h historyReporter) Created() string { if opts.human { return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago" } return h.ImageHistoryLayer.Created.Format(time.RFC3339) } -func (h historyreporter) Size() string { +func (h historyReporter) Size() string { s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3) i := strings.LastIndexFunc(s, unicode.IsNumber) return s[:i+1] + " " + s[i+1:] } -func (h historyreporter) CreatedBy() string { +func (h historyReporter) CreatedBy() string { if len(h.ImageHistoryLayer.CreatedBy) > 45 { return h.ImageHistoryLayer.CreatedBy[:45-3] + "..." } return h.ImageHistoryLayer.CreatedBy } -func (h historyreporter) ID() string { +func (h historyReporter) ID() string { if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 { return h.ImageHistoryLayer.ID[0:12] } diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go index fac06e324..0a972ea81 100644 --- a/cmd/podman/images/mount.go +++ b/cmd/podman/images/mount.go @@ -6,6 +6,7 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -24,7 +25,7 @@ var ( mountCommand = &cobra.Command{ Use: "mount [flags] [IMAGE...]", - Short: "Mount an images's root filesystem", + Short: "Mount an image's root filesystem", Long: mountDescription, RunE: mount, Example: `podman image mount imgID @@ -56,18 +57,18 @@ func init() { mountFlags(mountCommand.Flags()) } -func mount(_ *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) +func mount(cmd *cobra.Command, args []string) error { if len(args) > 0 && mountOpts.All { return errors.New("when using the --all switch, you may not pass any image names or IDs") } + reports, err := registry.ImageEngine().Mount(registry.GetContext(), args, mountOpts) if err != nil { return err } + if len(args) > 0 || mountOpts.All { + var errs utils.OutputErrors for _, r := range reports { if r.Err == nil { fmt.Println(r.Path) @@ -78,22 +79,22 @@ func mount(_ *cobra.Command, args []string) error { return errs.PrintErrors() } - switch mountOpts.Format { - case "json": + switch { + case parse.MatchesJSONFormat(mountOpts.Format): return printJSON(reports) - case "": - // do nothing + case mountOpts.Format == "": + break // default format default: - return errors.Errorf("unknown --format argument: %s", mountOpts.Format) + return errors.Errorf("unknown --format argument: %q", mountOpts.Format) } mrs := make([]mountReporter, 0, len(reports)) for _, r := range reports { mrs = append(mrs, mountReporter{r}) } - row := "{{.ID}} {{.Path}}\n" - format := "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("mounts").Parse(format) + + row := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}" + tmpl, err := template.New("mounts").Parse(row) if err != nil { return err } diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index b8d989d65..8edd776ce 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -2,15 +2,14 @@ package images import ( "os" - "reflect" - "strings" + "text/tabwriter" + "text/template" - "github.com/containers/buildah/pkg/formats" "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/containers/podman/v2/pkg/util/camelcase" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -86,6 +85,7 @@ func searchFlags(flags *pflag.FlagSet) { flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + flags.BoolVar(&searchOptions.ListTags, "list-tags", false, "List the tags of the input registry") } // imageSearch implements the command for searching images. @@ -102,6 +102,10 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit) } + if searchOptions.ListTags && len(searchOptions.Filters) != 0 { + return errors.Errorf("filters are not applicable to list tags result") + } + // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of @@ -121,40 +125,35 @@ func imageSearch(cmd *cobra.Command, args []string) error { return err } - format := genSearchFormat(searchOptions.Format) if len(searchReport) == 0 { return nil } - out := formats.StdoutTemplateArray{Output: searchToGeneric(searchReport), Template: format, Fields: searchHeaderMap()} - return out.Out() -} -// searchHeaderMap returns the headers of a SearchResult. -func searchHeaderMap() map[string]string { - s := new(entities.ImageSearchReport) - v := reflect.Indirect(reflect.ValueOf(s)) - values := make(map[string]string, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - values[key] = strings.ToUpper(strings.Join(camelcase.Split(value), " ")) + hdrs := report.Headers(entities.ImageSearchReport{}, nil) + row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" + if searchOptions.ListTags { + if len(searchOptions.Filters) != 0 { + return errors.Errorf("filters are not applicable to list tags result") + } + row = "{{.Name}}\t{{.Tag}}\n" } - return values -} + if cmd.Flags().Changed("format") { + row = report.NormalizeFormat(searchOptions.Format) + } + row = "{{range .}}" + row + "{{end}}" -func genSearchFormat(format string) string { - if 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" - return strings.Replace(format, `\t`, "\t", -1) + tmpl, err := template.New("search").Parse(row) + if err != nil { + return err } - return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t" -} + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() -func searchToGeneric(params []entities.ImageSearchReport) (genericParams []interface{}) { - for _, v := range params { - genericParams = append(genericParams, interface{}(v)) + if !cmd.Flags().Changed("format") { + if err := tmpl.Execute(w, hdrs); err != nil { + return errors.Wrapf(err, "failed to write search column headers") + } } - return genericParams + + return tmpl.Execute(w, searchReport) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index f29527412..658463650 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -4,10 +4,14 @@ import ( "context" "fmt" "os" + "regexp" "strings" + "text/tabwriter" + "text/template" - "github.com/containers/buildah/pkg/formats" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -24,6 +28,9 @@ const ( AllType = "all" ) +// Pull in configured json library +var json = registry.JSONLibrary() + // AddInspectFlagSet takes a command and adds the inspect flags and returns an // InspectOptions object. func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { @@ -80,7 +87,7 @@ func newInspector(options entities.InspectOptions) (*inspector, error) { // inspect inspects the specified container/image names or IDs. func (i *inspector) inspect(namesOrIDs []string) error { // data - dumping place for inspection results. - var data []interface{} //nolint + var data []interface{} // nolint var errs []error ctx := context.Background() @@ -134,15 +141,19 @@ func (i *inspector) inspect(namesOrIDs []string) error { data = []interface{}{} } - var out formats.Writer - if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat - out = formats.JSONStructArray{Output: data} - } else { - out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)} + var err error + switch { + case parse.MatchesJSONFormat(i.options.Format) || i.options.Format == "": + err = printJSON(data) + default: + row := inspectNormalize(i.options.Format) + row = "{{range . }}" + report.NormalizeFormat(row) + "{{end}}" + err = printTmpl(tmpType, row, data) } - if err := out.Out(); err != nil { + if err != nil { logrus.Errorf("Error printing inspect output: %v", err) } + if len(errs) > 0 { if len(errs) > 1 { for _, err := range errs[1:] { @@ -154,8 +165,22 @@ func (i *inspector) inspect(namesOrIDs []string) error { return nil } +func printJSON(data []interface{}) error { + enc := json.NewEncoder(os.Stdout) + return enc.Encode(data) +} + +func printTmpl(typ, row string, data []interface{}) error { + t, err := template.New(typ + " inspect").Parse(row) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + return t.Execute(w, data) +} + func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) { - var data []interface{} //nolint + var data []interface{} // nolint allErrs := []error{} for _, name := range namesOrIDs { ctrData, errs, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options) @@ -179,9 +204,11 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte return data, allErrs, nil } -func inspectFormat(row string) string { +func inspectNormalize(row string) string { + m := regexp.MustCompile(`{{\s*\.Id\s*}}`) + row = m.ReplaceAllString(row, "{{.ID}}") + r := strings.NewReplacer( - "{{.Id}}", formats.IDString, ".Src", ".Source", ".Dst", ".Destination", ".ImageID", ".Image", diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go index c5872def7..c36125948 100644 --- a/cmd/podman/networks/inspect.go +++ b/cmd/podman/networks/inspect.go @@ -3,12 +3,13 @@ package network import ( "encoding/json" "fmt" - "io" "os" - "strings" + "text/tabwriter" "text/template" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -39,31 +40,32 @@ func init() { flags.StringVarP(&networkInspectOptions.Format, "format", "f", "", "Pretty-print network to JSON or using a Go template") } -func networkInspect(cmd *cobra.Command, args []string) error { +func networkInspect(_ *cobra.Command, args []string) error { responses, err := registry.ContainerEngine().NetworkInspect(registry.Context(), args, entities.NetworkInspectOptions{}) if err != nil { return err } - b, err := json.MarshalIndent(responses, "", " ") - if err != nil { - return err - } - if strings.ToLower(networkInspectOptions.Format) == "json" || networkInspectOptions.Format == "" { - fmt.Println(string(b)) - } else { - var w io.Writer = os.Stdout - //There can be more than 1 in the inspect output. - format := "{{range . }}" + networkInspectOptions.Format + "{{end}}" - tmpl, err := template.New("inspectNetworks").Parse(format) + + switch { + case parse.MatchesJSONFormat(networkInspectOptions.Format) || networkInspectOptions.Format == "": + b, err := json.MarshalIndent(responses, "", " ") if err != nil { return err } - if err := tmpl.Execute(w, responses); err != nil { + fmt.Println(string(b)) + default: + row := report.NormalizeFormat(networkInspectOptions.Format) + // There can be more than 1 in the inspect output. + row = "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("inspectNetworks").Parse(row) + if err != nil { return err } - if flusher, ok := w.(interface{ Flush() error }); ok { - return flusher.Flush() - } + + w := tabwriter.NewWriter(os.Stdout, 8, 2, 0, ' ', 0) + defer w.Flush() + + return tmpl.Execute(w, responses) } return nil } diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 54a6d0677..976d720ee 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -60,6 +60,7 @@ func init() { flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") + flags.StringSliceVar(&kubeOptions.ConfigMaps, "configmap", []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") } _ = flags.MarkHidden("signature-policy") } diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index bc20352b0..cad15d10f 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -3,9 +3,13 @@ package pods import ( "context" "fmt" + "os" + "text/tabwriter" + "text/template" - "github.com/containers/buildah/pkg/formats" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -57,11 +61,19 @@ func inspect(cmd *cobra.Command, args []string) error { if err != nil { return err } - var data interface{} = responses - var out formats.Writer = formats.JSONStruct{Output: data} - if inspectOptions.Format != "json" { - out = formats.StdoutTemplate{Output: data, Template: inspectOptions.Format} + + if parse.MatchesJSONFormat(inspectOptions.Format) { + enc := json.NewEncoder(os.Stdout) + return enc.Encode(responses) + } + + row := report.NormalizeFormat(inspectOptions.Format) + + t, err := template.New("pod inspect").Parse(row) + if err != nil { + return err } - return out.Out() + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + return t.Execute(w, *responses) } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 7b755cb22..b7952e6e3 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -3,7 +3,6 @@ package pods import ( "context" "fmt" - "io" "os" "sort" "strings" @@ -11,7 +10,9 @@ import ( "text/template" "time" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" @@ -34,10 +35,9 @@ var ( ) var ( - defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED" - inputFilters []string - noTrunc bool - psInput entities.PodPSOptions + inputFilters []string + noTrunc bool + psInput entities.PodPSOptions ) func init() { @@ -62,11 +62,6 @@ func init() { } func pods(cmd *cobra.Command, _ []string) error { - var ( - w io.Writer = os.Stdout - row string - ) - if psInput.Quiet && len(psInput.Format) > 0 { return errors.New("quiet and format cannot be used together") } @@ -89,80 +84,79 @@ func pods(cmd *cobra.Command, _ []string) error { return err } - if psInput.Format == "json" { + switch { + case parse.MatchesJSONFormat(psInput.Format): b, err := json.MarshalIndent(responses, "", " ") if err != nil { return err } fmt.Println(string(b)) return nil + case psInput.Quiet: + for _, p := range responses { + fmt.Println(p.Id) + } + return nil } + // Formatted output below lpr := make([]ListPodReporter, 0, len(responses)) for _, r := range responses { lpr = append(lpr, ListPodReporter{r}) } - headers, row := createPodPsOut() - if psInput.Quiet { - row = "{{.Id}}\n" - } - if cmd.Flag("format").Changed { - row = psInput.Format - if !strings.HasPrefix(row, "\n") { - row += "\n" - } - } - format := "{{range . }}" + row + "{{end}}" - if !psInput.Quiet && !cmd.Flag("format").Changed { - format = headers + format + + headers := report.Headers(ListPodReporter{}, map[string]string{ + "ContainerIds": "IDS", + "ContainerNames": "NAMES", + "ContainerStatuses": "STATUS", + "Namespace": "NAMESPACES", + "NumberOfContainers": "# OF CONTAINERS", + "InfraId": "INFRA ID", + }) + row := podPsFormat() + if cmd.Flags().Changed("format") { + row = report.NormalizeFormat(psInput.Format) } - tmpl, err := template.New("listPods").Parse(format) + row = "{{range . }}" + row + "{{end}}" + + tmpl, err := template.New("listPods").Parse(row) if err != nil { return err } - if !psInput.Quiet { - w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - } - if err := tmpl.Execute(w, lpr); err != nil { - return err - } - if flusher, ok := w.(interface{ Flush() error }); ok { - return flusher.Flush() + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + + if !psInput.Quiet && !cmd.Flag("format").Changed { + if err := tmpl.Execute(w, headers); err != nil { + return err + } } - return nil + return tmpl.Execute(w, lpr) } -func createPodPsOut() (string, string) { - var row string - headers := defaultHeaders - row += "{{.Id}}" - - row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}" +func podPsFormat() string { + row := []string{"{{.Id}}", "{{.Name}}", "{{.Status}}", "{{.Created}}}"} if psInput.CtrIds { - headers += "\tIDS" - row += "\t{{.ContainerIds}}" + row = append(row, "{{.ContainerIds}}") } + if psInput.CtrNames { - headers += "\tNAMES" - row += "\t{{.ContainerNames}}" + row = append(row, "{{.ContainerNames}}") } + if psInput.CtrStatus { - headers += "\tSTATUS" - row += "\t{{.ContainerStatuses}}" + row = append(row, "{{.ContainerStatuses}}") } + if psInput.Namespace { - headers += "\tCGROUP\tNAMESPACES" - row += "\t{{.Cgroup}}\t{{.Namespace}}" + row = append(row, "{{.Cgroup}}", "{{.Namespace}}") } - if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds { - headers += "\t# OF CONTAINERS" - row += "\t{{.NumberOfContainers}}" + if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds { + row = append(row, "{{.NumberOfContainers}}") } - headers += "\tINFRA ID\n" - row += "\t{{.InfraId}}\n" - return headers, row + return strings.Join(row, "\t") + "\n" } // ListPodReporter is a struct for pod ps output @@ -180,7 +174,7 @@ func (l ListPodReporter) Labels() map[string]string { return l.ListPodsReport.Labels } -// NumberofContainers returns an int representation for +// NumberOfContainers returns an int representation for // the number of containers belonging to the pod func (l ListPodReporter) NumberOfContainers() int { return len(l.Containers) @@ -192,7 +186,7 @@ func (l ListPodReporter) ID() string { } // Id returns the Pod id -func (l ListPodReporter) Id() string { //nolint +func (l ListPodReporter) Id() string { // nolint if noTrunc { return l.ListPodsReport.Id } @@ -206,7 +200,7 @@ func (l ListPodReporter) InfraID() string { // InfraId returns the infra container id for the pod // depending on trunc -func (l ListPodReporter) InfraId() string { //nolint +func (l ListPodReporter) InfraId() string { // nolint if len(l.ListPodsReport.InfraId) == 0 { return "" } diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 1d916dbfa..2f59e4e47 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -4,18 +4,16 @@ import ( "context" "fmt" "os" - "reflect" - "strings" "text/tabwriter" "text/template" "time" "github.com/buger/goterm" - "github.com/containers/buildah/pkg/formats" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/containers/podman/v2/pkg/util/camelcase" "github.com/spf13/cobra" ) @@ -67,11 +65,18 @@ func stats(cmd *cobra.Command, args []string) error { return err } - format := statsOptions.Format - doJSON := strings.ToLower(format) == formats.JSONString - header := getPodStatsHeader(format) + row := report.NormalizeFormat(statsOptions.Format) + doJSON := parse.MatchesJSONFormat(row) - for { + headers := report.Headers(entities.PodStatsReport{}, map[string]string{ + "CPU": "CPU %", + "MemUsage": "MEM USAGE/ LIMIT", + "MEM": "MEM %", + "NET IO": "NET IO", + "BlockIO": "BLOCK IO", + }) + + for ; ; time.Sleep(time.Second) { reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions) if err != nil { return err @@ -87,16 +92,17 @@ func stats(cmd *cobra.Command, args []string) error { goterm.MoveCursor(1, 1) goterm.Flush() } - if len(format) == 0 { + if cmd.Flags().Changed("format") { + if err := printFormattedPodStatsLines(headers, row, reports); err != nil { + return err + } + } else { printPodStatsLines(reports) - } else if err := printFormattedPodStatsLines(format, reports, header); err != nil { - return err } } if statsOptions.NoStream { break } - time.Sleep(time.Second) } return nil @@ -115,72 +121,32 @@ 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 { + if len(stats) == 0 { + fmt.Fprintf(w, outFormat, "--", "--", "--", "--", "--", "--", "--", "--", "--") + } else { + for _, i := range stats { 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 { +func printFormattedPodStatsLines(headerNames []map[string]string, row string, stats []*entities.PodStatsReport) 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, "") - } + row = "{{range .}}" + row + "{{end}}" - // Spit out the data rows now - dataTmpl, err := template.New("data").Parse(format) + tmpl, err := template.New("pod stats").Parse(row) 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() - -} + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + defer 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 + if err := tmpl.Execute(w, headerNames); err != nil { + return err } - return headerNames + return tmpl.Execute(w, stats) } diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 1e73f7540..6293fa17d 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -11,6 +11,7 @@ import ( "github.com/containers/common/pkg/config" "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/domain/entities" "github.com/containers/podman/v2/pkg/parallel" "github.com/containers/podman/v2/pkg/rootless" @@ -84,7 +85,7 @@ func init() { func Execute() { if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil { - fmt.Fprintln(os.Stderr, "Error:", err.Error()) + fmt.Fprintln(os.Stderr, formatError(err)) } else if registry.GetExitCode() == registry.ExecErrorCodeGeneric { // The exitCode modified from registry.ExecErrorCodeGeneric, // indicates an application @@ -331,3 +332,19 @@ func resolveDestination() (string, string, string) { } return cfg.Engine.ActiveService, uri, ident } + +func formatError(err error) string { + var message string + if errors.Cause(err) == define.ErrOCIRuntime { + // OCIRuntimeErrors include the reason for the failure in the + // second to last message in the error chain. + message = fmt.Sprintf( + "Error: %s: %s", + define.ErrOCIRuntime.Error(), + strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()), + ) + } else { + message = "Error: " + err.Error() + } + return message +} diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go new file mode 100644 index 000000000..0473128df --- /dev/null +++ b/cmd/podman/root_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "strings" + "testing" + + "github.com/containers/podman/v2/libpod/define" + "github.com/pkg/errors" +) + +func TestFormatError(t *testing.T) { + err := errors.New("unknown error") + output := formatError(err) + expected := fmt.Sprintf("Error: %v", err) + + if output != expected { + t.Errorf("Expected \"%s\" to equal \"%s\"", output, err.Error()) + } +} + +func TestFormatOCIError(t *testing.T) { + expectedPrefix := "Error: " + expectedSuffix := "OCI runtime output" + err := errors.Wrap(define.ErrOCIRuntime, expectedSuffix) + output := formatError(err) + + if !strings.HasPrefix(output, expectedPrefix) { + t.Errorf("Expected \"%s\" to start with \"%s\"", output, expectedPrefix) + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("Expected \"%s\" to end with \"%s\"", output, expectedSuffix) + } +} diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index d198e51a7..18765a499 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -3,13 +3,14 @@ package volumes import ( "context" "fmt" - "io" "os" "strings" "text/tabwriter" "text/template" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -55,7 +56,6 @@ func init() { } func list(cmd *cobra.Command, args []string) error { - var w io.Writer = os.Stdout if cliOpts.Quiet && cmd.Flag("format").Changed { return errors.New("quiet and format flags cannot be used together") } @@ -73,40 +73,40 @@ func list(cmd *cobra.Command, args []string) error { if err != nil { return err } - if cliOpts.Format == "json" { - return outputJSON(responses) - } - if len(responses) < 1 { + switch { + case parse.MatchesJSONFormat(cliOpts.Format): + return outputJSON(responses) + case len(responses) < 1: return nil } - // "\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" - cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1) + return outputTemplate(cmd, responses) +} + +func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) error { + headers := report.Headers(entities.VolumeListReport{}, map[string]string{ + "Name": "VOLUME NAME", + }) + + row := report.NormalizeFormat(cliOpts.Format) if cliOpts.Quiet { - cliOpts.Format = "{{.Name}}\n" + row = "{{.Name}}\n" } - headers := "DRIVER\tVOLUME NAME\n" - row := cliOpts.Format - if !strings.HasSuffix(cliOpts.Format, "\n") { - row += "\n" - } - format := "{{range . }}" + row + "{{end}}" - if !cliOpts.Quiet && !cmd.Flag("format").Changed { - w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) - format = headers + format - } - tmpl, err := template.New("listVolume").Parse(format) + row = "{{range . }}" + row + "{{end}}" + + tmpl, err := template.New("list volume").Parse(row) if err != nil { return err } - if err := tmpl.Execute(w, responses); err != nil { - return err - } - if flusher, ok := w.(interface{ Flush() error }); ok { - return flusher.Flush() + w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) + defer w.Flush() + + if !cliOpts.Quiet && !cmd.Flag("format").Changed { + if err := tmpl.Execute(w, headers); err != nil { + return errors.Wrapf(err, "failed to write report column headers") + } } - return nil + return tmpl.Execute(w, responses) } func outputJSON(vols []*entities.VolumeListReport) error { |