diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2022-09-13 20:26:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-13 20:26:24 +0200 |
commit | ad529f31d82796c17c5a3dde5fed20b84d5bd256 (patch) | |
tree | 6d6f50a8e9c9d5d9de2b5bb248d3f54b8ee755fe | |
parent | 6e382d9ec2e6eb79a72537544341e496368b6c63 (diff) | |
parent | 9d41b95d72ef29870e6c325557c89c2db818a371 (diff) | |
download | podman-ad529f31d82796c17c5a3dde5fed20b84d5bd256.tar.gz podman-ad529f31d82796c17c5a3dde5fed20b84d5bd256.tar.bz2 podman-ad529f31d82796c17c5a3dde5fed20b84d5bd256.zip |
Merge pull request #15673 from Luap99/template
Fix go template parsing with "\n" in it
-rw-r--r-- | cmd/podman/auto-update.go | 28 | ||||
-rw-r--r-- | cmd/podman/inspect/inspect.go | 33 | ||||
-rw-r--r-- | cmd/podman/machine/info.go | 12 | ||||
-rw-r--r-- | cmd/podman/machine/inspect.go | 20 | ||||
-rw-r--r-- | cmd/podman/machine/list.go | 40 | ||||
-rw-r--r-- | cmd/podman/networks/list.go | 31 | ||||
-rw-r--r-- | cmd/podman/secrets/inspect.go | 13 | ||||
-rw-r--r-- | cmd/podman/secrets/list.go | 32 | ||||
-rw-r--r-- | cmd/podman/system/info.go | 13 | ||||
-rw-r--r-- | cmd/podman/system/version.go | 19 | ||||
-rw-r--r-- | cmd/podman/volumes/list.go | 30 | ||||
-rw-r--r-- | pkg/machine/e2e/inspect_test.go | 8 | ||||
-rw-r--r-- | test/e2e/volume_ls_test.go | 5 | ||||
-rw-r--r-- | test/system/610-format.bats | 169 |
14 files changed, 286 insertions, 167 deletions
diff --git a/cmd/podman/auto-update.go b/cmd/podman/auto-update.go index 88ef0ec88..6a0446422 100644 --- a/cmd/podman/auto-update.go +++ b/cmd/podman/auto-update.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "os" - "strings" "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" @@ -104,15 +103,15 @@ func reportsToOutput(allReports []*entities.AutoUpdateReport) []autoUpdateOutput } func writeTemplate(allReports []*entities.AutoUpdateReport, inputFormat string) error { - var format string - var printHeader bool + rpt := report.New(os.Stdout, "auto-update") + defer rpt.Flush() output := reportsToOutput(allReports) + var err error switch inputFormat { case "": - rows := []string{"{{.Unit}}", "{{.Container}}", "{{.Image}}", "{{.Policy}}", "{{.Updated}}"} - format = "{{range . }}" + strings.Join(rows, "\t") + "\n{{end -}}" - printHeader = true + format := "{{range . }}\t{{.Unit}}\t{{.Container}}\t{{.Image}}\t{{.Policy}}\t{{.Updated}}\n{{end -}}" + rpt, err = rpt.Parse(report.OriginPodman, format) case "json": prettyJSON, err := json.MarshalIndent(output, "", " ") if err != nil { @@ -121,26 +120,17 @@ func writeTemplate(allReports []*entities.AutoUpdateReport, inputFormat string) fmt.Println(string(prettyJSON)) return nil default: - format = "{{range . }}" + inputFormat + "\n{{end -}}" + rpt, err = rpt.Parse(report.OriginUser, inputFormat) } - - tmpl, err := report.NewTemplate("auto-update").Parse(format) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - defer w.Flush() - if printHeader { + if rpt.RenderHeaders { headers := report.Headers(autoUpdateOutput{}, nil) - if err := tmpl.Execute(w, headers); err != nil { + if err := rpt.Execute(headers); err != nil { return err } } - - return tmpl.Execute(w, output) + return rpt.Execute(output) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index ccabd7614..22b2c9055 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -8,7 +8,6 @@ import ( "os" "regexp" "strings" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -16,7 +15,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -176,13 +174,18 @@ func (i *inspector) inspect(namesOrIDs []string) error { } default: // Landing here implies user has given a custom --format - row := inspectNormalize(i.options.Format, tmpType) - row = report.NormalizeFormat(row) - row = report.EnforceRange(row) - err = printTmpl(tmpType, row, data) + var rpt *report.Formatter + format := inspectNormalize(i.options.Format, i.options.Type) + rpt, err = report.New(os.Stdout, "inspect").Parse(report.OriginUser, format) + if err != nil { + return err + } + defer rpt.Flush() + + err = rpt.Execute(data) } if err != nil { - logrus.Errorf("Printing inspect output: %v", err) + errs = append(errs, fmt.Errorf("printing inspect output: %w", err)) } if len(errs) > 0 { @@ -205,22 +208,6 @@ func printJSON(data interface{}) error { return enc.Encode(data) } -func printTmpl(typ, row string, data []interface{}) error { - // We cannot use c/common/reports here, too many levels of interface{} - t, err := template.New(typ + " inspect").Funcs(template.FuncMap(report.DefaultFuncs)).Parse(row) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - err = t.Execute(w, data) - w.Flush() - return err -} - func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) { var data []interface{} allErrs := []error{} diff --git a/cmd/podman/machine/info.go b/cmd/podman/machine/info.go index 1151f9e86..9c7454379 100644 --- a/cmd/podman/machine/info.go +++ b/cmd/podman/machine/info.go @@ -5,7 +5,6 @@ package machine import ( "fmt" - "html/template" "os" "runtime" @@ -75,13 +74,16 @@ func info(cmd *cobra.Command, args []string) error { } fmt.Println(string(b)) case cmd.Flags().Changed("format"): - tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs)) - inFormat = report.NormalizeFormat(inFormat) - tmpl, err := tmpl.Parse(inFormat) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + + // Use OriginUnknown so it does not add an extra range since it + // will only be called for a single element and not a slice. + rpt, err = rpt.Parse(report.OriginUnknown, inFormat) if err != nil { return err } - return tmpl.Execute(os.Stdout, info) + return rpt.Execute(info) default: b, err := yaml.Marshal(info) if err != nil { diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go index d69c382f2..410bb3889 100644 --- a/cmd/podman/machine/inspect.go +++ b/cmd/podman/machine/inspect.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/machine" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -66,28 +65,23 @@ func inspect(cmd *cobra.Command, args []string) error { } vms = append(vms, *ii) } + switch { case cmd.Flag("format").Changed: - row := report.NormalizeFormat(inspectFlag.format) - row = report.EnforceRange(row) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - tmpl, err := report.NewTemplate("Machine inspect").Parse(row) + rpt, err := rpt.Parse(report.OriginUser, inspectFlag.format) if err != nil { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - - if err := tmpl.Execute(w, vms); err != nil { - logrus.Error(err) + if err := rpt.Execute(vms); err != nil { + errs = append(errs, err) } - w.Flush() default: if err := printJSON(vms); err != nil { - logrus.Error(err) + errs = append(errs, err) } } return errs.PrintErrors() diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index dd4a86697..ddc9ce246 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -53,7 +53,7 @@ func init() { flags := lsCmd.Flags() formatFlagName := "format" - flags.StringVar(&listFlag.format, formatFlagName, "{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n", "Format volume output using JSON or a Go template") + flags.StringVar(&listFlag.format, formatFlagName, "{{range .}}{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n{{end -}}", "Format volume output using JSON or a Go template") _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ListReporter{})) flags.BoolVar(&listFlag.noHeading, "noheading", false, "Do not print headers") flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Show only machine names") @@ -66,10 +66,6 @@ func list(cmd *cobra.Command, args []string) error { err error ) - if listFlag.quiet { - listFlag.format = "{{.Name}}\n" - } - provider := GetSystemDefaultProvider() listResponse, err = provider.List(opts) if err != nil { @@ -115,37 +111,29 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.ListReporter) erro "Memory": "MEMORY", "DiskSize": "DISK SIZE", }) - printHeader := !listFlag.noHeading - if listFlag.quiet { - printHeader = false - } - var row string + + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + + var err error switch { - case cmd.Flags().Changed("format"): - row = cmd.Flag("format").Value.String() - printHeader = report.HasTable(row) - row = report.NormalizeFormat(row) + case cmd.Flag("format").Changed: + rpt, err = rpt.Parse(report.OriginUser, listFlag.format) + case listFlag.quiet: + rpt, err = rpt.Parse(report.OriginUser, "{{.Name}}\n") default: - row = cmd.Flag("format").Value.String() + rpt, err = rpt.Parse(report.OriginPodman, listFlag.format) } - format := report.EnforceRange(row) - - tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - defer w.Flush() - if printHeader { - if err := tmpl.Execute(w, headers); err != nil { + if rpt.RenderHeaders && !listFlag.noHeading { + if err := rpt.Execute(headers); err != nil { return fmt.Errorf("failed to write report column headers: %w", err) } } - return tmpl.Execute(w, responses) + return rpt.Execute(responses) } func strTime(t time.Time) string { diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 5d4be2a81..951583cfb 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -122,36 +122,27 @@ func templateOut(cmd *cobra.Command, responses []types.Network) error { "ID": "network id", }) - renderHeaders := report.HasTable(networkListOptions.Format) - var row string + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + + var err error switch { - case cmd.Flags().Changed("format"): - row = report.NormalizeFormat(networkListOptions.Format) + case cmd.Flag("format").Changed: + rpt, err = rpt.Parse(report.OriginUser, networkListOptions.Format) default: - // 'podman network ls' equivalent to 'podman network ls --format="table {{.ID}} {{.Name}} {{.Version}} {{.Plugins}}" ' - row = "{{.ID}}\t{{.Name}}\t{{.Driver}}\n" - renderHeaders = true + rpt, err = rpt.Parse(report.OriginPodman, "{{range .}}{{.ID}}\t{{.Name}}\t{{.Driver}}\n{{end -}}") } - format := report.EnforceRange(row) - - tmpl, err := report.NewTemplate("list").Parse(format) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - defer w.Flush() noHeading, _ := cmd.Flags().GetBool("noheading") - if !noHeading && renderHeaders { - if err := tmpl.Execute(w, headers); err != nil { - return err + if rpt.RenderHeaders && !noHeading { + if err := rpt.Execute(headers); err != nil { + return fmt.Errorf("failed to write report column headers: %w", err) } } - return tmpl.Execute(w, nlprs) + return rpt.Execute(nlprs) } // ListPrintReports returns the network list report diff --git a/cmd/podman/secrets/inspect.go b/cmd/podman/secrets/inspect.go index c99e555ba..f4c395b0f 100644 --- a/cmd/podman/secrets/inspect.go +++ b/cmd/podman/secrets/inspect.go @@ -47,20 +47,15 @@ func inspect(cmd *cobra.Command, args []string) error { } if cmd.Flags().Changed("format") { - row := report.NormalizeFormat(format) - formatted := report.EnforceRange(row) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - tmpl, err := report.NewTemplate("inspect").Parse(formatted) + rpt, err := rpt.Parse(report.OriginUser, format) if err != nil { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - defer w.Flush() - if err := tmpl.Execute(w, inspected); err != nil { + if err := rpt.Execute(inspected); err != nil { return err } } else { diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index afa9b8887..1833cc544 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -46,7 +46,7 @@ func init() { flags := lsCmd.Flags() formatFlagName := "format" - flags.StringVar(&listFlag.format, formatFlagName, "{{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}\t\n", "Format volume output using Go template") + flags.StringVar(&listFlag.format, formatFlagName, "{{range .}}{{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}\n{{end -}}", "Format volume output using Go template") _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.SecretInfoReport{})) filterFlagName := "filter" @@ -105,31 +105,25 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.SecretListReport) "UpdatedAt": "UPDATED", }) - row := cmd.Flag("format").Value.String() - if cmd.Flags().Changed("format") { - row = report.NormalizeFormat(row) - } - format := report.EnforceRange(row) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - tmpl, err := report.NewTemplate("list").Parse(format) - if err != nil { - return err + var err error + switch { + case cmd.Flag("format").Changed: + rpt, err = rpt.Parse(report.OriginUser, listFlag.format) + default: + rpt, err = rpt.Parse(report.OriginPodman, listFlag.format) } - - w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - defer w.Flush() - - if cmd.Flags().Changed("format") && !report.HasTable(listFlag.format) { - listFlag.noHeading = true - } - if !listFlag.noHeading { - if err := tmpl.Execute(w, headers); err != nil { + noHeading, _ := cmd.Flags().GetBool("noheading") + if rpt.RenderHeaders && !noHeading { + if err := rpt.Execute(headers); err != nil { return fmt.Errorf("failed to write report column headers: %w", err) } } - return tmpl.Execute(w, responses) + return rpt.Execute(responses) } diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 296fa4def..9613d5e56 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -3,7 +3,6 @@ package system import ( "fmt" "os" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -86,14 +85,16 @@ func info(cmd *cobra.Command, args []string) error { } fmt.Println(string(b)) case cmd.Flags().Changed("format"): - // Cannot use report.New() as it enforces {{range .}} for OriginUser templates - tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs)) - inFormat = report.NormalizeFormat(inFormat) - tmpl, err := tmpl.Parse(inFormat) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + + // Use OriginUnknown so it does not add an extra range since it + // will only be called for a single element and not a slice. + rpt, err = rpt.Parse(report.OriginUnknown, inFormat) if err != nil { return err } - return tmpl.Execute(os.Stdout, info) + return rpt.Execute(info) default: b, err := yaml.Marshal(info) if err != nil { diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 7202b2c08..33ab0f757 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "strings" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -12,6 +11,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -53,22 +53,25 @@ func version(cmd *cobra.Command, args []string) error { } if cmd.Flag("format").Changed { - // Cannot use report.New() as it enforces {{range .}} for OriginUser templates - tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs)) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - versionFormat = report.NormalizeFormat(versionFormat) - tmpl, err := tmpl.Parse(versionFormat) + // Use OriginUnknown so it does not add an extra range since it + // will only be called for a single element and not a slice. + rpt, err = rpt.Parse(report.OriginUnknown, versionFormat) if err != nil { return err } - if err := tmpl.Execute(os.Stdout, versions); err != nil { + if err := rpt.Execute(versions); err != nil { + // only log at debug since we fall back to the client only template + logrus.Debugf("Failed to execute template: %v", err) // On Failure, assume user is using older version of podman version --format and check client versionFormat = strings.ReplaceAll(versionFormat, ".Server.", ".") - tmpl, err := tmpl.Parse(versionFormat) + rpt, err := rpt.Parse(report.OriginUnknown, versionFormat) if err != nil { return err } - if err := tmpl.Execute(os.Stdout, versions.Client); err != nil { + if err := rpt.Execute(versions.Client); err != nil { return err } } diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index 06118513d..70041834b 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -55,7 +55,7 @@ func init() { _ = lsCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters) formatFlagName := "format" - flags.StringVar(&cliOpts.Format, formatFlagName, "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template") + flags.StringVar(&cliOpts.Format, formatFlagName, "{{range .}}{{.Driver}}\t{{.Name}}\n{{end -}}", "Format volume output using Go template") _ = lsCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.VolumeListReport{})) flags.Bool("noheading", false, "Do not print headers") @@ -95,34 +95,28 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) "Name": "VOLUME NAME", }) - var row string + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + + var err error switch { + case cmd.Flag("format").Changed: + rpt, err = rpt.Parse(report.OriginUser, cliOpts.Format) case cliOpts.Quiet: - row = "{{.Name}}\n" - case cmd.Flags().Changed("format"): - row = report.NormalizeFormat(cliOpts.Format) + rpt, err = rpt.Parse(report.OriginUser, "{{.Name}}\n") default: - row = cmd.Flag("format").Value.String() + rpt, err = rpt.Parse(report.OriginPodman, cliOpts.Format) } - format := report.EnforceRange(row) - - tmpl, err := report.NewTemplate("list").Parse(format) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - defer w.Flush() - if !(noHeading || cliOpts.Quiet || cmd.Flag("format").Changed) { - if err := tmpl.Execute(w, headers); err != nil { + if (rpt.RenderHeaders) && !noHeading { + if err := rpt.Execute(headers); err != nil { return fmt.Errorf("failed to write report column headers: %w", err) } } - return tmpl.Execute(w, responses) + return rpt.Execute(responses) } func outputJSON(vols []*entities.VolumeListReport) error { diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index 0ab928205..fac9f7ebe 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -75,5 +75,13 @@ var _ = Describe("podman machine stop", func() { Expect(err).To(BeNil()) Expect(inspectSession).To(Exit(0)) Expect(inspectSession.Bytes()).To(ContainSubstring(name)) + + // check invalid template returns error + inspect = new(inspectMachine) + inspect = inspect.withFormat("{{.Abcde}}") + inspectSession, err = mb.setName(name).setCmd(inspect).run() + Expect(err).To(BeNil()) + Expect(inspectSession).To(Exit(125)) + Expect(inspectSession.errorToString()).To(ContainSubstring("can't evaluate field Abcde in type machine.InspectInfo")) }) }) diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index dcfb13f4e..24ea50b26 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -75,7 +75,10 @@ var _ = Describe("Podman volume ls", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToStringArray()).To(HaveLen(1), session.OutputToString()) + arr := session.OutputToStringArray() + Expect(arr).To(HaveLen(2)) + Expect(arr[0]).To(ContainSubstring("NAME")) + Expect(arr[1]).To(ContainSubstring("myvol")) }) It("podman ls volume with --filter flag", func() { diff --git a/test/system/610-format.bats b/test/system/610-format.bats new file mode 100644 index 000000000..096d0228b --- /dev/null +++ b/test/system/610-format.bats @@ -0,0 +1,169 @@ +#!/usr/bin/env bats -*- bats -*- +# +# PR #15673: For all commands that accept --format '{{.GoTemplate}}', +# invoke with --format '{{"\n"}}' and make sure they don't choke. +# + +load helpers + +function teardown() { + # In case test fails: standard teardown does not wipe machines or secrets + run_podman '?' machine rm -f mymachine + run_podman '?' secret rm mysecret + + basic_teardown +} + +# Most commands can't just be run with --format; they need an argument or +# option. This table defines what those are. +# +# FIXME: once you've finished fixing them all, remove the SKIPs (just +# remove the entire lines, except for pod-inspect, just remove the SKIP +# but leave "mypod") +extra_args_table=" +history | $IMAGE +image history | $IMAGE +image inspect | $IMAGE +container inspect | mycontainer +machine inspect | mymachine + +volume inspect | -a +secret inspect | mysecret +network inspect | podman +ps | -a + +image search | sdfsdf +search | sdfsdf + +pod inspect | mypod + +container stats | --no-stream +pod stats | --no-stream +stats | --no-stream +events | --stream=false --events-backend=file +" + +# Main test loop. Recursively runs 'podman [subcommand] help', looks for: +# > '[command]', which indicates, recurse; or +# > '--format', in which case we +# > check autocompletion, look for Go templates, in which case we +# > run the command with --format '{{"\n"}}' and make sure it passes +function check_subcommand() { + for cmd in $(_podman_commands "$@"); do + # Special case: 'podman machine' can't be run as root. No override. + if [[ "$cmd" = "machine" ]]; then + if ! is_rootless; then + unset extra_args["podman machine inspect"] + continue + fi + fi + + # Human-readable podman command string, with multiple spaces collapsed + command_string="podman $* $cmd" + command_string=${command_string// / } # 'podman x' -> 'podman x' + + # Run --help, decide if this is a subcommand with subcommands + run_podman "$@" $cmd --help + local full_help="$output" + + # The line immediately after 'Usage:' gives us a 1-line synopsis + usage=$(echo "$full_help" | grep -A1 '^Usage:' | tail -1) + assert "$usage" != "" "podman $cmd: no Usage message found" + + # Strip off the leading command string; we no longer need it + usage=$(sed -e "s/^ $command_string \?//" <<<"$usage") + + # If usage ends in '[command]', recurse into subcommands + if expr "$usage" : '\[command\]' >/dev/null; then + # (except for 'podman help', which is a special case) + if [[ $cmd != "help" ]]; then + check_subcommand "$@" $cmd + fi + continue + fi + + # Not a subcommand-subcommand. Look for --format option + if [[ ! "$output" =~ "--format" ]]; then + continue + fi + + # Have --format. Make sure it's a Go-template option, not like --push + run_podman __completeNoDesc "$@" "$cmd" --format '{{.' + if [[ ! "$output" =~ \{\{\.[A-Z] ]]; then + continue + fi + + # Got one. + dprint "$command_string has --format" + + # Whatever is needed to make a runnable command + local extra=${extra_args[$command_string]} + if [[ -n "$extra" ]]; then + # Cross off our list + unset extra_args["$command_string"] + fi + + # This is what does the work. We run with '?' so we can offer + # better error messages than just "exited with error status". + run_podman '?' "$@" "$cmd" $extra --format '{{"\n"}}' + + # Output must always be empty. + # + # - If you see "unterminated quoted string" here, there's a + # regression, and you need to fix --format (see PR #15673) + # + # - If you see any other error, it probably means that someone + # added a new podman subcommand that supports --format but + # needs some sort of option or argument to actually run. + # See 'extra_args_table' at the top of this script. + # + assert "$output" = "" "$command_string --format '{{\"\n\"}}'" + + # *Now* check exit status. This should never, ever, ever trigger! + # If it does, it means the podman command failed without an err msg! + assert "$status" = "0" \ + "$command_string --format '{{\"\n\"}}' failed with no output!" + done +} + +# Test entry point +@test "check Go template formatting" { + skip_if_remote + if is_ubuntu; then + skip 'ubuntu VMs do not have qemu (exec: "qemu-system-x86_64": executable file not found in $PATH)' + fi + + # Convert the table at top to an associative array, keyed on subcommand + declare -A extra_args + while read subcommand extra; do + extra_args["podman $subcommand"]=$extra + done < <(parse_table "$extra_args_table") + + # Setup: some commands need a container, pod, machine, or secret + run_podman run -d --name mycontainer $IMAGE top + run_podman pod create mypod + run_podman secret create mysecret /etc/hosts + if is_rootless; then + run_podman machine init --image-path=/dev/null mymachine + fi + + # Run the test + check_subcommand + + # Clean up + run_podman pod rm mypod + run_podman rmi $(pause_image) + run_podman rm -f -t0 mycontainer + run_podman secret rm mysecret + if is_rootless; then + run_podman machine rm -f mymachine + fi + + # Make sure there are no leftover commands in our table - this would + # indicate a typo in the table, or a flaw in our logic such that + # we're not actually recursing. + local leftovers="${!extra_args[@]}" + assert "$leftovers" = "" "Did not find (or test) subcommands:" +} + +# vim: filetype=sh |