aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/common/util.go2
-rw-r--r--cmd/podman/containers/mount.go21
-rw-r--r--cmd/podman/containers/ps.go53
-rw-r--r--cmd/podman/containers/rm.go13
-rw-r--r--cmd/podman/generate/systemd.go7
-rw-r--r--cmd/podman/images/diff.go9
-rw-r--r--cmd/podman/images/history.go57
-rw-r--r--cmd/podman/images/mount.go27
-rw-r--r--cmd/podman/images/search.go61
-rw-r--r--cmd/podman/inspect/inspect.go49
-rw-r--r--cmd/podman/networks/inspect.go38
-rw-r--r--cmd/podman/play/kube.go1
-rw-r--r--cmd/podman/pods/inspect.go24
-rw-r--r--cmd/podman/pods/ps.go108
-rw-r--r--cmd/podman/pods/stats.go94
-rw-r--r--cmd/podman/root.go19
-rw-r--r--cmd/podman/root_test.go34
-rw-r--r--cmd/podman/volumes/list.go54
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 {