From b490905f26bcbdd23c0c431482604ad563ea7948 Mon Sep 17 00:00:00 2001
From: Jhon Honce <jhonce@redhat.com>
Date: Mon, 5 Oct 2020 15:46:50 -0700
Subject: [PATCH] Port commands to V2 --format 'table...'

 * 'containers mount'
 * 'image history'
 * 'images mount'
 * 'images search'
 * Correct spelling errors

Signed-off-by: Jhon Honce <jhonce@redhat.com>
---
 cmd/podman/containers/mount.go | 21 ++++++-----
 cmd/podman/images/history.go   | 69 +++++++++++++++++-----------------
 cmd/podman/images/mount.go     | 27 ++++++-------
 cmd/podman/images/search.go    | 51 ++++++++++---------------
 test/e2e/search_test.go        |  6 +--
 test/system/060-mount.bats     |  2 +-
 6 files changed, 82 insertions(+), 94 deletions(-)

diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index f2df5e99e9..c4dfb513f1 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/images/history.go b/cmd/podman/images/history.go
index 30abf0adaf..fa4b368c60 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))
-	for _, l := range results.Layers {
-		hr = append(hr, historyreporter{l})
-	}
-	// 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 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"
+	hr := make([]historyReporter, 0, len(results.Layers))
+	for _, l := range results.Layers {
+		hr = append(hr, historyReporter{l})
 	}
-	format := hdr + "{{range . }}" + row + "{{end}}"
+
+	hdrs := report.Headers(historyReporter{}, map[string]string{
+		"CreatedBy": "CREATED BY",
+	})
+
+	// Defaults
+	row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
+	switch {
+	case cmd.Flags().Changed("format"):
+		row = report.NormalizeFormat(opts.format)
+	case opts.quiet:
+		row = "{{.ID}}\n"
+	}
+	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 fac06e3247..0a972ea81c 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 b8d989d655..b8f5905850 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"
@@ -120,41 +119,29 @@ func imageSearch(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		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 cmd.Flags().Changed("format") {
+		row = report.NormalizeFormat(searchOptions.Format)
 	}
-	return values
-}
+	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/test/e2e/search_test.go b/test/e2e/search_test.go
index 497949bbc4..043da90599 100644
--- a/test/e2e/search_test.go
+++ b/test/e2e/search_test.go
@@ -237,7 +237,7 @@ registries = ['{{.Host}}:{{.Port}}']`
 	})
 
 	It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() {
-		SkipIfRemote("--tls-verify is not supportedon podman-remote search")
+		SkipIfRemote("--tls-verify is not supported on podman-remote search")
 		if podmanTest.Host.Arch == "ppc64le" {
 			Skip("No registry image for ppc64le")
 		}
@@ -278,7 +278,7 @@ registries = ['{{.Host}}:{{.Port}}']`
 	})
 
 	It("podman search doesn't attempt HTTP if force secure is true", func() {
-		SkipIfRemote("--tls-verify is not supportedon podman-remote search")
+		SkipIfRemote("--tls-verify is not supported on podman-remote search")
 		if podmanTest.Host.Arch == "ppc64le" {
 			Skip("No registry image for ppc64le")
 		}
@@ -317,7 +317,7 @@ registries = ['{{.Host}}:{{.Port}}']`
 	})
 
 	It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() {
-		SkipIfRemote("--tls-verify is not supportedon podman-remote search")
+		SkipIfRemote("--tls-verify is not supported on podman-remote search")
 		if podmanTest.Host.Arch == "ppc64le" {
 			Skip("No registry image for ppc64le")
 		}
diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats
index 75c88e4ad4..f11aff773c 100644
--- a/test/system/060-mount.bats
+++ b/test/system/060-mount.bats
@@ -56,7 +56,7 @@ load helpers
 
     # 'image mount', no args, tells us what's mounted
     run_podman image mount
-    is "$output" "$IMAGE $mount_path" "podman image mount with no args"
+    is "$output" "$IMAGE *$mount_path" "podman image mount with no args"
 
     # Clean up
     run_podman image umount $IMAGE