diff options
65 files changed, 480 insertions, 360 deletions
@@ -1,30 +1,35 @@ approvers: + - Luap99 + - TomSweeneyRedHat + - ashley-cui - baude - edsantiago + - flouthoc - giuseppe - jwhonce - - Luap99 + - lsm5 - mheon - mtrmac - rhatdan - saschagrunert - - TomSweeneyRedHat - umohnani8 - vrothberg - zhangguanzhang reviewers: - - ashley-cui + - Luap99 + - QiWang19 + - TomSweeneyRedHat - baude + - cdoern - edsantiago + - flouthoc - giuseppe - jwhonce - - Luap99 + - lsm5 - mheon - mtrmac - - QiWang19 - rhatdan - saschagrunert - - TomSweeneyRedHat - umohnani8 - vrothberg - zhangguanzhang diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 4cb29383a..cb3efe592 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -1289,3 +1289,9 @@ func AutocompleteCheckpointCompressType(cmd *cobra.Command, args []string, toCom types := []string{"gzip", "none", "zstd"} return types, cobra.ShellCompDirectiveNoFileComp } + +// AutocompleteCompressionFormat - Autocomplete compression-format type options. +func AutocompleteCompressionFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + types := []string{"gzip", "zstd", "zstd:chunked"} + return types, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 0397b456f..271fc4c6b 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -81,7 +81,7 @@ func init() { validate.AddLatestFlag(containerMountCommand, &mountOpts.Latest) } -func mount(_ *cobra.Command, args []string) error { +func mount(cmd *cobra.Command, args []string) error { if len(args) > 0 && mountOpts.Latest { return errors.Errorf("--latest and containers cannot be used together") } @@ -116,18 +116,14 @@ func mount(_ *cobra.Command, args []string) error { mrs = append(mrs, mountReporter{r}) } - format := "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}" - tmpl, err := report.NewTemplate("mounts").Parse(format) - if err != nil { - return err - } + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - w, err := report.NewWriterDefault(os.Stdout) + rpt, err = rpt.Parse(report.OriginPodman, "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}") if err != nil { return err } - defer w.Flush() - return tmpl.Execute(w, mrs) + return rpt.Execute(mrs) } func printJSON(reports []*entities.ContainerMountReport) error { diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 712de327c..cebe61b5a 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -220,30 +220,28 @@ func ps(cmd *cobra.Command, _ []string) error { hdrs, format := createPsOut() + var origin report.Origin noHeading, _ := cmd.Flags().GetBool("noheading") if cmd.Flags().Changed("format") { noHeading = noHeading || !report.HasTable(listOpts.Format) - format = report.NormalizeFormat(listOpts.Format) - format = report.EnforceRange(format) + format = listOpts.Format + origin = report.OriginUser + } else { + origin = report.OriginPodman } ns := strings.NewReplacer(".Namespaces.", ".") format = ns.Replace(format) - tmpl, err := report.NewTemplate("list").Parse(format) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) + rpt, err := report.New(os.Stdout, cmd.Name()).Parse(origin, format) if err != nil { return err } - defer w.Flush() + defer rpt.Flush() headers := func() error { return nil } if !noHeading { headers = func() error { - return tmpl.Execute(w, hdrs) + return rpt.Execute(hdrs) } } @@ -268,10 +266,10 @@ func ps(cmd *cobra.Command, _ []string) error { if err := headers(); err != nil { return err } - if err := tmpl.Execute(w, responses); err != nil { + if err := rpt.Execute(responses); err != nil { return err } - if err := w.Flush(); err != nil { + if err := rpt.Flush(); err != nil { // we usually do not care about Flush() failures but here do not loop if Flush() has failed return err } @@ -282,7 +280,7 @@ func ps(cmd *cobra.Command, _ []string) error { if err := headers(); err != nil { return err } - if err := tmpl.Execute(w, responses); err != nil { + if err := rpt.Execute(responses); err != nil { return err } } diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index d21feaabc..9fba51597 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -126,14 +126,14 @@ func stats(cmd *cobra.Command, args []string) error { if report.Error != nil { return report.Error } - if err := outputStats(report.Stats); err != nil { + if err := outputStats(cmd, report.Stats); err != nil { return err } } return nil } -func outputStats(reports []define.ContainerStats) error { +func outputStats(cmd *cobra.Command, reports []define.ContainerStats) error { headers := report.Headers(define.ContainerStats{}, map[string]string{ "ID": "ID", "UpTime": "CPU TIME", @@ -158,32 +158,27 @@ func outputStats(reports []define.ContainerStats) error { if report.IsJSON(statsOptions.Format) { return outputJSON(stats) } - format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\t{{.UpTime}}\t{{.AVGCPU}}\n" - if len(statsOptions.Format) > 0 { - format = report.NormalizeFormat(statsOptions.Format) - } - format = report.EnforceRange(format) - tmpl, err := report.NewTemplate("stats").Parse(format) - if err != nil { - return err - } + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - w, err := report.NewWriterDefault(os.Stdout) + var err error + if cmd.Flags().Changed("format") { + rpt, err = rpt.Parse(report.OriginUser, statsOptions.Format) + } else { + format := "{{range .}}{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\t{{.UpTime}}\t{{.AVGCPU}}\n{{end -}}" + rpt, err = rpt.Parse(report.OriginPodman, format) + } if err != nil { return err } - defer w.Flush() - if len(statsOptions.Format) < 1 { - if err := tmpl.Execute(w, headers); err != nil { + if rpt.RenderHeaders { + if err := rpt.Execute(headers); err != nil { return err } } - if err := tmpl.Execute(w, stats); err != nil { - return err - } - return nil + return rpt.Execute(stats) } type containerStats struct { diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index 808c6c494..963a251bb 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -77,7 +77,7 @@ func init() { validate.AddLatestFlag(containerTopCommand, &topOptions.Latest) } -func top(_ *cobra.Command, args []string) error { +func top(cmd *cobra.Command, args []string) error { if topOptions.ListDescriptors { descriptors, err := util.GetContainerPidInformationDescriptors() if err != nil { @@ -103,15 +103,13 @@ func top(_ *cobra.Command, args []string) error { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } + rpt := report.New(os.Stdout, cmd.Name()).Init(os.Stdout, 12, 2, 2, ' ', 0) + defer rpt.Flush() for _, proc := range topResponse.Value { - if _, err := fmt.Fprintln(w, proc); err != nil { + if _, err := fmt.Fprintln(rpt.Writer(), proc); err != nil { return err } } - return w.Flush() + return nil } diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index cc7b1b4eb..ac693a87b 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -1,7 +1,6 @@ package images import ( - "context" "fmt" "os" "strings" @@ -79,7 +78,7 @@ func historyFlags(cmd *cobra.Command) { } func history(cmd *cobra.Command, args []string) error { - results, err := registry.ImageEngine().History(context.Background(), args[0], entities.ImageHistoryOptions{}) + results, err := registry.ImageEngine().History(registry.Context(), args[0], entities.ImageHistoryOptions{}) if err != nil { return err } @@ -111,37 +110,32 @@ func history(cmd *cobra.Command, args []string) error { hr = append(hr, historyReporter{l}) } - hdrs := report.Headers(historyReporter{}, map[string]string{ - "CreatedBy": "CREATED BY", - }) + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - // 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" + rpt, err = rpt.Parse(report.OriginUser, "{{range .}}{{.ID}}\n{{end -}}") + case cmd.Flags().Changed("format"): + rpt, err = rpt.Parse(report.OriginUser, cmd.Flag("format").Value.String()) + default: + format := "{{range .}}{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n{{end -}}" + rpt, err = rpt.Parse(report.OriginPodman, format) } - format := report.EnforceRange(row) - - tmpl, err := report.NewTemplate("history").Parse(format) if err != nil { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - defer w.Flush() + if rpt.RenderHeaders { + hdrs := report.Headers(historyReporter{}, map[string]string{ + "CreatedBy": "CREATED BY", + }) - if !opts.quiet && !cmd.Flags().Changed("format") { - if err := tmpl.Execute(w, hdrs); err != nil { + if err := rpt.Execute(hdrs); err != nil { return errors.Wrapf(err, "failed to write report column headers") } } - return tmpl.Execute(w, hr) + return rpt.Execute(hr) } type historyReporter struct { diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 01286daf2..61514daa7 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -117,7 +117,7 @@ func images(cmd *cobra.Command, args []string) error { listOptions.Filter = append(listOptions.Filter, "reference="+args[0]) } - if cmd.Flag("sort").Changed && !sortFields.Contains(listFlag.sort) { + if cmd.Flags().Changed("sort") && !sortFields.Contains(listFlag.sort) { return fmt.Errorf("\"%s\" is not a valid field for sorting. Choose from: %s", listFlag.sort, sortFields.String()) } @@ -140,7 +140,7 @@ func images(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("format") && !report.HasTable(listFlag.format) { listFlag.noHeading = true } - return writeTemplate(imgs) + return writeTemplate(cmd, imgs) } } @@ -186,38 +186,31 @@ func writeJSON(images []imageReporter) error { return nil } -func writeTemplate(imgs []imageReporter) error { +func writeTemplate(cmd *cobra.Command, imgs []imageReporter) error { hdrs := report.Headers(imageReporter{}, map[string]string{ "ID": "IMAGE ID", "ReadOnly": "R/O", }) - var format string - if listFlag.format == "" { - format = lsFormatFromFlags(listFlag) - } else { - format = report.NormalizeFormat(listFlag.format) - format = report.EnforceRange(format) - } + 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 + if cmd.Flags().Changed("format") { + rpt, err = rpt.Parse(report.OriginUser, cmd.Flag("format").Value.String()) + } else { + rpt, err = rpt.Parse(report.OriginPodman, lsFormatFromFlags(listFlag)) } - - w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - defer w.Flush() - if !listFlag.noHeading { - if err := tmpl.Execute(w, hdrs); err != nil { + if rpt.RenderHeaders && !listFlag.noHeading { + if err := rpt.Execute(hdrs); err != nil { return err } } - - return tmpl.Execute(w, imgs) + return rpt.Execute(imgs) } func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go index 89c00cb70..cdeb9eecf 100644 --- a/cmd/podman/images/mount.go +++ b/cmd/podman/images/mount.go @@ -87,7 +87,7 @@ func mount(cmd *cobra.Command, args []string) error { case report.IsJSON(mountOpts.Format): return printJSON(reports) case mountOpts.Format == "": - break // default format + break // see default format below default: return errors.Errorf("unknown --format argument: %q", mountOpts.Format) } @@ -97,19 +97,12 @@ func mount(cmd *cobra.Command, args []string) error { mrs = append(mrs, mountReporter{r}) } - row := "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}" - tmpl, err := report.NewTemplate("mounts").Parse(row) + rpt, err := report.New(os.Stdout, cmd.Name()).Parse(report.OriginPodman, "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}") if err != nil { return err } - - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - defer w.Flush() - - return tmpl.Execute(w, mrs) + defer rpt.Flush() + return rpt.Execute(mrs) } func printJSON(reports []*entities.ImageMountReport) error { diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index cf787a71f..37ace3ffe 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -108,6 +108,10 @@ func pushFlags(cmd *cobra.Command) { flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + compressionFormat := "compression-format" + flags.StringVar(&pushOptions.CompressionFormat, compressionFormat, "", "compression format to use") + _ = cmd.RegisterFlagCompletionFunc(compressionFormat, common.AutocompleteCompressionFormat) + if registry.IsRemote() { _ = flags.MarkHidden("cert-dir") _ = flags.MarkHidden("compress") diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index c9a4793aa..0791ac02c 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -149,9 +149,9 @@ func imageSearch(cmd *cobra.Command, args []string) error { searchReport[i].Description = d } - hdrs := report.Headers(entities.ImageSearchReport{}, nil) - renderHeaders := true - var row string + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() + switch { case searchOptions.ListTags: if len(searchOptions.Filters) != 0 { @@ -161,39 +161,30 @@ func imageSearch(cmd *cobra.Command, args []string) error { listTagsEntries := buildListTagsJSON(searchReport) return printArbitraryJSON(listTagsEntries) } - row = "{{.Name}}\t{{.Tag}}\n" + rpt, err = rpt.Parse(report.OriginPodman, "{{range .}}{{.Name}}\t{{.Tag}}\n{{end -}}") case isJSON: return printArbitraryJSON(searchReport) case cmd.Flags().Changed("format"): - renderHeaders = report.HasTable(searchOptions.Format) - row = report.NormalizeFormat(searchOptions.Format) + rpt, err = rpt.Parse(report.OriginUser, searchOptions.Format) default: - row = "{{.Name}}\t{{.Description}}" + row := "{{.Name}}\t{{.Description}}" if searchOptions.Compatible { row += "\t{{.Stars}}\t{{.Official}}\t{{.Automated}}" } - row += "\n" + row = "{{range . }}" + row + "\n{{end -}}" + rpt, err = rpt.Parse(report.OriginPodman, row) } - format := report.EnforceRange(row) - - tmpl, err := report.NewTemplate("search").Parse(format) if err != nil { return err } - w, err := report.NewWriterDefault(os.Stdout) - if err != nil { - return err - } - defer w.Flush() - - if renderHeaders { - if err := tmpl.Execute(w, hdrs); err != nil { - return errors.Wrapf(err, "failed to write search column headers") + if rpt.RenderHeaders { + hdrs := report.Headers(entities.ImageSearchReport{}, nil) + if err := rpt.Execute(hdrs); err != nil { + return errors.Wrapf(err, "failed to write report column headers") } } - - return tmpl.Execute(w, searchReport) + return rpt.Execute(searchReport) } func printArbitraryJSON(v interface{}) error { diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go index c0e56f504..04ea24ca5 100644 --- a/cmd/podman/images/trust_show.go +++ b/cmd/podman/images/trust_show.go @@ -48,11 +48,12 @@ func showTrust(cmd *cobra.Command, args []string) error { if err != nil { return err } - if showTrustOptions.Raw { + + switch { + case showTrustOptions.Raw: fmt.Println(string(trust.Raw)) return nil - } - if showTrustOptions.JSON { + case showTrustOptions.JSON: b, err := json.MarshalIndent(trust.Policies, "", " ") if err != nil { return err @@ -60,23 +61,13 @@ func showTrust(cmd *cobra.Command, args []string) error { fmt.Println(string(b)) return nil } + rpt := report.New(os.Stdout, cmd.Name()) + defer rpt.Flush() - format := "{{range . }}{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n{{end -}}" - tmpl, err := report.NewTemplate("list").Parse(format) - if err != nil { - return err - } - - w, err := report.NewWriterDefault(os.Stdout) + rpt, err = rpt.Parse(report.OriginPodman, + "{{range . }}{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n{{end -}}") if err != nil { return err } - - if err := tmpl.Execute(w, trust.Policies); err != nil { - return err - } - if err := w.Flush(); err != nil { - return err - } - return nil + return rpt.Execute(trust.Policies) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index 64b586388..c982b1b7f 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -215,8 +215,10 @@ func (i *inspector) inspect(namesOrIDs []string) error { case report.IsJSON(i.options.Format) || i.options.Format == "": err = printJSON(data) default: + // Landing here implies user has given a custom --format row := inspectNormalize(i.options.Format) - row = "{{range . }}" + report.NormalizeFormat(row) + "{{end -}}" + row = report.NormalizeFormat(row) + row = report.EnforceRange(row) err = printTmpl(tmpType, row, data) } if err != nil { diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index d569f4db0..774ab4fd0 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -116,7 +116,15 @@ func outputTemplate(cmd *cobra.Command, responses []*machineReporter) error { "DiskSize": "DISK SIZE", }) - row := report.NormalizeFormat(listFlag.format) + var row string + switch { + case cmd.Flags().Changed("format"): + row = cmd.Flag("format").Value.String() + listFlag.noHeading = !report.HasTable(row) + row = report.NormalizeFormat(row) + default: + row = cmd.Flag("format").Value.String() + } format := report.EnforceRange(row) tmpl, err := report.NewTemplate("list").Parse(format) @@ -130,10 +138,6 @@ func outputTemplate(cmd *cobra.Command, responses []*machineReporter) error { } 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 { return errors.Wrapf(err, "failed to write report column headers") diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 124a17d5d..6f1a7742a 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -84,7 +84,7 @@ func networkList(cmd *cobra.Command, args []string) error { // table or other format output default: - err = templateOut(responses, cmd) + err = templateOut(cmd, responses) } return err @@ -105,7 +105,7 @@ func jsonOut(responses []types.Network) error { return nil } -func templateOut(responses []types.Network, cmd *cobra.Command) error { +func templateOut(cmd *cobra.Command, responses []types.Network) error { nlprs := make([]ListPrintReports, 0, len(responses)) for _, r := range responses { nlprs = append(nlprs, ListPrintReports{r}) @@ -120,14 +120,16 @@ func templateOut(responses []types.Network, cmd *cobra.Command) error { }) renderHeaders := report.HasTable(networkListOptions.Format) - var row, format string - if cmd.Flags().Changed("format") { + var row string + switch { + case cmd.Flags().Changed("format"): row = report.NormalizeFormat(networkListOptions.Format) - } else { // 'podman network ls' equivalent to 'podman network ls --format="table {{.ID}} {{.Name}} {{.Version}} {{.Plugins}}" ' - renderHeaders = true + default: + // 'podman network ls' equivalent to 'podman network ls --format="table {{.ID}} {{.Name}} {{.Version}} {{.Plugins}}" ' row = "{{.ID}}\t{{.Name}}\t{{.Driver}}\n" + renderHeaders = true } - format = report.EnforceRange(row) + format := report.EnforceRange(row) tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index 96eaec3b9..d4b8df90e 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -64,11 +64,13 @@ func inspect(cmd *cobra.Command, args []string) error { } if report.IsJSON(inspectOptions.Format) { + json.MarshalIndent(responses, "", " ") enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(responses) } + // cmd.Flags().Changed("format") must be true to reach this code row := report.NormalizeFormat(inspectOptions.Format) t, err := report.NewTemplate("inspect").Parse(row) diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 60aadf224..808ec31b3 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -136,11 +136,12 @@ func pods(cmd *cobra.Command, _ []string) error { renderHeaders = report.HasTable(psInput.Format) row = report.NormalizeFormat(psInput.Format) } + format := report.EnforceRange(row) + noHeading, _ := cmd.Flags().GetBool("noheading") if noHeading { renderHeaders = false } - format := report.EnforceRange(row) tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index ba2c1495b..a7bba3064 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -67,9 +67,7 @@ func stats(cmd *cobra.Command, args []string) error { return err } - row := report.NormalizeFormat(statsOptions.Format) - doJSON := report.IsJSON(row) - + doJSON := report.IsJSON(cmd.Flag("format").Value.String()) headers := report.Headers(entities.PodStatsReport{}, map[string]string{ "CPU": "CPU %", "MemUsage": "MEM USAGE/ LIMIT", @@ -96,6 +94,8 @@ func stats(cmd *cobra.Command, args []string) error { goterm.Flush() } if cmd.Flags().Changed("format") { + row := report.NormalizeFormat(statsOptions.Format) + row = report.EnforceRange(row) if err := printFormattedPodStatsLines(headers, row, reports); err != nil { return err } @@ -143,8 +143,6 @@ func printFormattedPodStatsLines(headerNames []map[string]string, row string, st return nil } - row = report.EnforceRange(row) - tmpl, err := report.NewTemplate("stats").Parse(row) if err != nil { return err diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index f136de4ab..255d9ae1a 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -71,7 +71,10 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.SecretListReport) "UpdatedAt": "UPDATED", }) - row := report.NormalizeFormat(listFlag.format) + row := cmd.Flag("format").Value.String() + if cmd.Flags().Changed("format") { + row = report.NormalizeFormat(row) + } format := report.EnforceRange(row) tmpl, err := report.NewTemplate("list").Parse(format) diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go index f1f7657ad..2710142a8 100644 --- a/cmd/podman/system/connection/list.go +++ b/cmd/podman/system/connection/list.go @@ -82,7 +82,7 @@ func list(cmd *cobra.Command, _ []string) error { return rows[i].Name < rows[j].Name }) - format := "{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\n" + var format string switch { case report.IsJSON(cmd.Flag("format").Value.String()): buf, err := registry.JSONLibrary().MarshalIndent(rows, "", " ") @@ -90,11 +90,10 @@ func list(cmd *cobra.Command, _ []string) error { fmt.Println(string(buf)) } return err + case cmd.Flags().Changed("format"): + format = report.NormalizeFormat(cmd.Flag("format").Value.String()) default: - if cmd.Flag("format").Changed { - format = cmd.Flag("format").Value.String() - format = report.NormalizeFormat(format) - } + format = "{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\n" } format = report.EnforceRange(format) diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 3443978d6..87b806503 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -59,7 +59,7 @@ func version(cmd *cobra.Command, args []string) error { } defer w.Flush() - if cmd.Flag("format").Changed { + if cmd.Flags().Changed("format") { row := report.NormalizeFormat(versionFormat) tmpl, err := report.NewTemplate("version 2.0.0").Parse(row) if err != nil { diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index 0243054af..c372527de 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -97,9 +97,14 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) "Name": "VOLUME NAME", }) - row := report.NormalizeFormat(cliOpts.Format) - if cliOpts.Quiet { + var row string + switch { + case cliOpts.Quiet: row = "{{.Name}}\n" + case cmd.Flags().Changed("format"): + row = report.NormalizeFormat(cliOpts.Format) + default: + row = cmd.Flag("format").Value.String() } format := report.EnforceRange(row) diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md index 55f294158..19c64a7e3 100644 --- a/docs/source/markdown/podman-push.1.md +++ b/docs/source/markdown/podman-push.1.md @@ -71,6 +71,10 @@ Please refer to containers-certs.d(5) for details. (This option is not available Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type, compressed or uncompressed, as source) Note: This flag can only be set when using the **dir** transport +#### **--compression-format** *COMPRESSION* + +Specifies the compression format to use. Supported values are: `gzip`, `zstd` and `zstd:chunked`. The default is `gzip`. + #### **--digestfile** *Digestfile* After copying the image, write the digest of the resulting image to the file. (This option is not available with the remote Podman client) diff --git a/docs/source/markdown/podman-unshare.1.md b/docs/source/markdown/podman-unshare.1.md index 5676b1be7..01393a862 100644 --- a/docs/source/markdown/podman-unshare.1.md +++ b/docs/source/markdown/podman-unshare.1.md @@ -35,7 +35,6 @@ Print usage statement Join the rootless network namespace used for CNI and netavark networking. It can be used to connect to a rootless container via IP address (bridge networking). This is otherwise not possible from the host network namespace. -_Note: Using this option with more than one unshare session can have unexpected results._ ## Exit Codes @@ -57,13 +56,13 @@ the exit codes follow the `chroot` standard, see below: **127** Executing a _contained command_ and the _command_ cannot be found - $ podman run busybox foo; echo $? + $ podman unshare foo; echo $? Error: fork/exec /usr/bin/bogus: no such file or directory 127 **Exit code** _contained command_ exit code - $ podman run busybox /bin/sh -c 'exit 3'; echo $? + $ podman unshare /bin/sh -c 'exit 3'; echo $? 3 ## EXAMPLE diff --git a/libpod/container.go b/libpod/container.go index 482af43f3..2b74a1943 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -939,6 +939,11 @@ func (c *Container) cGroupPath() (string, error) { procPath := fmt.Sprintf("/proc/%d/cgroup", c.state.PID) lines, err := ioutil.ReadFile(procPath) if err != nil { + // If the file doesn't exist, it means the container could have been terminated + // so report it. + if os.IsNotExist(err) { + return "", errors.Wrapf(define.ErrCtrStopped, "cannot get cgroup path unless container %s is running", c.ID()) + } return "", err } diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 0d4cba85e..d4f4ddfc1 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -4,6 +4,7 @@ package libpod import ( "bufio" + "fmt" "os" "strconv" "strings" @@ -11,6 +12,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/psgo" + "github.com/google/shlex" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -51,7 +53,21 @@ func (c *Container) Top(descriptors []string) ([]string, error) { return nil, psgoErr } - output, err = c.execPS(descriptors) + // Note that the descriptors to ps(1) must be shlexed (see #12452). + psDescriptors := []string{} + for _, d := range descriptors { + shSplit, err := shlex.Split(d) + if err != nil { + return nil, fmt.Errorf("parsing ps args: %v", err) + } + for _, s := range shSplit { + if s != "" { + psDescriptors = append(psDescriptors, s) + } + } + } + + output, err = c.execPS(psDescriptors) if err != nil { return nil, errors.Wrapf(err, "error executing ps(1) in the container") } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 7d1214183..b734b9c95 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -322,17 +322,14 @@ func (r *RootlessNetNS) Do(toRun func() error) error { // Cleanup the rootless network namespace if needed. // It checks if we have running containers with the bridge network mode. -// Cleanup() will try to lock RootlessNetNS, therefore you have to call -// it with an unlocked lock. +// Cleanup() expects that r.Lock is locked func (r *RootlessNetNS) Cleanup(runtime *Runtime) error { _, err := os.Stat(r.dir) if os.IsNotExist(err) { // the directory does not exists no need for cleanup return nil } - r.Lock.Lock() - defer r.Lock.Unlock() - running := func(c *Container) bool { + activeNetns := func(c *Container) bool { // no bridge => no need to check if !c.config.NetMode.IsBridge() { return false @@ -352,15 +349,18 @@ func (r *RootlessNetNS) Cleanup(runtime *Runtime) error { return false } - state := c.state.State - return state == define.ContainerStateRunning + // only check for an active netns, we cannot use the container state + // because not running does not mean that the netns does not need cleanup + // only if the netns is empty we know that we do not need cleanup + return c.state.NetNS != nil } - ctrs, err := runtime.GetContainersWithoutLock(running) + ctrs, err := runtime.GetContainersWithoutLock(activeNetns) if err != nil { return err } - // no cleanup if we found containers - if len(ctrs) > 0 { + // no cleanup if we found no other containers with a netns + // we will always find one container (the container cleanup that is currently calling us) + if len(ctrs) > 1 { return nil } logrus.Debug("Cleaning up rootless network namespace") @@ -809,10 +809,10 @@ func (r *Runtime) teardownNetwork(ns string, opts types.NetworkOptions) error { if rootlessNetNS != nil { // execute the cni setup in the rootless net ns err = rootlessNetNS.Do(tearDownPod) - rootlessNetNS.Lock.Unlock() - if err == nil { - err = rootlessNetNS.Cleanup(r) + if cerr := rootlessNetNS.Cleanup(r); cerr != nil { + logrus.WithError(err).Error("failed to cleanup rootless netns") } + rootlessNetNS.Lock.Unlock() } else { err = tearDownPod() } diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index af8b6b63d..4533fddeb 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -409,14 +409,20 @@ func GetImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) return } - var summaries = make([]*entities.ImageSummary, len(images)) - for j, img := range images { + + summaries := make([]*entities.ImageSummary, 0, len(images)) + for _, img := range images { + // If the image is a manifest list, extract as much as we can. + if isML, _ := img.IsManifestList(r.Context()); isML { + continue + } + is, err := handlers.ImageToImageSummary(img) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) return } - summaries[j] = is + summaries = append(summaries, is) } utils.WriteResponse(w, http.StatusOK, summaries) } diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 25770fdfc..be6e5ab55 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -346,6 +346,11 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } c = tmpFile.Name() } + cfDir := filepath.Dir(c) + if absDir, err := filepath.EvalSymlinks(cfDir); err == nil { + name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator))) + c = filepath.Join(absDir, name) + } containerfile, err := filepath.Abs(c) if err != nil { logrus.Errorf("Cannot find absolute path of %v: %v", c, err) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 54f7b5d45..8b0fd2b85 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -208,6 +208,8 @@ type ImagePushOptions struct { SkipTLSVerify types.OptionalBool // Progress to get progress notifications Progress chan types.ProgressProperties + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat string } // ImageSearchOptions are the arguments for searching images. diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 8b44b869a..7a3451a7d 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -18,6 +18,7 @@ import ( "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -305,6 +306,14 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri pushOptions.SignBy = options.SignBy pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + if options.CompressionFormat != "" { + algo, err := compression.AlgorithmByName(options.CompressionFormat) + if err != nil { + return err + } + pushOptions.CompressionFormat = &algo + } + if !options.Quiet { pushOptions.Writer = os.Stderr } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 7da7754f2..e6c9d850b 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -365,9 +365,12 @@ func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options e if err != nil { return err } - // make sure to unlock, unshare can run for a long time + // Make sure to unlock, unshare can run for a long time. rootlessNetNS.Lock.Unlock() - defer rootlessNetNS.Cleanup(ic.Libpod) + // We do not want to cleanup the netns after unshare. + // The problem is that we cannot know if we need to cleanup and + // secondly unshare should allow user to setup the namespace with + // special things, e.g. potentially macvlan or something like that. return rootlessNetNS.Do(unshare) } return unshare() diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 1b022b912..df5788099 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -329,6 +329,14 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access) } + for k, v := range s.WeightDevice { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return nil, errors.Wrapf(err, "failed to inspect '%s' in --blkio-weight-device", k) + } + g.AddLinuxResourcesBlockIOWeightDevice((int64(unix.Major(uint64(statT.Rdev)))), (int64(unix.Minor(uint64(statT.Rdev)))), *v.Weight) + } + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) g.ClearProcessEnv() diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7a572e730..637a6a8dd 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -85,7 +85,7 @@ func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) ( } if len(c.BlkIOWeightDevice) > 0 { - if err := parseWeightDevices(s, c.BlkIOWeightDevice); err != nil { + if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil { return nil, err } hasLimits = true @@ -791,29 +791,30 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start return &hc, nil } -func parseWeightDevices(s *specgen.SpecGenerator, weightDevs []string) error { +func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) { + wd := make(map[string]specs.LinuxWeightDevice) for _, val := range weightDevs { split := strings.SplitN(val, ":", 2) if len(split) != 2 { - return fmt.Errorf("bad format: %s", val) + return nil, fmt.Errorf("bad format: %s", val) } if !strings.HasPrefix(split[0], "/dev/") { - return fmt.Errorf("bad format for device path: %s", val) + return nil, fmt.Errorf("bad format for device path: %s", val) } weight, err := strconv.ParseUint(split[1], 10, 0) if err != nil { - return fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", val) } if weight > 0 && (weight < 10 || weight > 1000) { - return fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", val) } w := uint16(weight) - s.WeightDevice[split[0]] = specs.LinuxWeightDevice{ + wd[split[0]] = specs.LinuxWeightDevice{ Weight: &w, LeafWeight: nil, } } - return nil + return wd, nil } func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index b7bcaf81d..e67f559f3 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -10,6 +10,13 @@ t GET libpod/images/json 200 \ .[0].Id~[0-9a-f]\\{64\\} iid=$(jq -r '.[0].Id' <<<"$output") +# Create an empty manifest and make sure it is not listed +# in the compat endpoint. +t GET images/json 200 length=1 +podman manifest create foo +t GET images/json 200 length=1 +t GET libpod/images/json 200 length=2 + t GET libpod/images/$iid/exists 204 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204 t GET libpod/images/${iid}abcdef/exists 404 \ diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index e068a6582..5a2640f33 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -192,7 +192,7 @@ var _ = Describe("Podman checkpoint", func() { ps.WaitWithDefaultTimeout() Expect(ps).Should(Exit(0)) Expect(ps.OutputToString()).To(ContainSubstring(session1.OutputToString())) - Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + Expect(ps.OutputToString()).To(Not(ContainSubstring(session2.OutputToString()))) result = podmanTest.Podman([]string{"container", "restore", "second"}) result.WaitWithDefaultTimeout() @@ -228,8 +228,8 @@ var _ = Describe("Podman checkpoint", func() { ps := podmanTest.Podman([]string{"ps", "-q", "--no-trunc"}) ps.WaitWithDefaultTimeout() Expect(ps).Should(Exit(0)) - Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse()) - Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + Expect(ps.OutputToString()).To(Not(ContainSubstring(session1.OutputToString()))) + Expect(ps.OutputToString()).To(Not(ContainSubstring(session2.OutputToString()))) result = podmanTest.Podman([]string{"container", "restore", "-a"}) result.WaitWithDefaultTimeout() @@ -1297,8 +1297,8 @@ var _ = Describe("Podman checkpoint", func() { }) ps.WaitWithDefaultTimeout() Expect(ps).Should(Exit(0)) - Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse()) - Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse()) + Expect(ps.OutputToString()).To(Not(ContainSubstring(session1.OutputToString()))) + Expect(ps.OutputToString()).To(Not(ContainSubstring(session2.OutputToString()))) result = podmanTest.Podman([]string{ "container", diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index b751c338f..a09666468 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -204,8 +204,7 @@ var _ = Describe("Podman commit", func() { inspect.WaitWithDefaultTimeout() Expect(inspect).Should(Exit(0)) image := inspect.InspectImageJSON() - _, ok := image[0].Config.Volumes["/foo"] - Expect(ok).To(BeFalse()) + Expect(image[0].Config.Volumes).To(Not(HaveKey("/foo"))) }) It("podman commit with volume mounts and --include-volumes", func() { @@ -224,8 +223,7 @@ var _ = Describe("Podman commit", func() { inspect.WaitWithDefaultTimeout() Expect(inspect).Should(Exit(0)) image := inspect.InspectImageJSON() - _, ok := image[0].Config.Volumes["/foo"] - Expect(ok).To(BeTrue(), ".Config.Volumes[/foo]") + Expect(image[0].Config.Volumes).To(HaveKey("/foo")) r := podmanTest.Podman([]string{"run", "newimage"}) r.WaitWithDefaultTimeout() @@ -250,7 +248,7 @@ var _ = Describe("Podman commit", func() { for _, v := range image[0].Config.Env { envMap[v] = true } - Expect(envMap["TEST=1=1-01=9.01"]).To(BeTrue(), "envMap[TEST=1=1-01=9.01]") + Expect(envMap).To(HaveKey("TEST=1=1-01=9.01")) }) It("podman commit container and print id to external file", func() { diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 63cb4f091..a411a860b 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -36,7 +36,6 @@ var ( PODMAN_BINARY string //nolint:golint,stylecheck INTEGRATION_ROOT string //nolint:golint,stylecheck CGROUP_MANAGER = "systemd" //nolint:golint,stylecheck - ARTIFACT_DIR = "/tmp/.artifacts" //nolint:golint,stylecheck RESTORE_IMAGES = []string{ALPINE, BB, nginx} //nolint:golint,stylecheck defaultWaitTimeout = 90 CGROUPSV2, _ = cgroups.IsCgroup2UnifiedMode() //nolint:golint,stylecheck @@ -46,7 +45,7 @@ var ( type PodmanTestIntegration struct { PodmanTest ConmonBinary string - CrioRoot string + Root string CNIConfigDir string OCIRuntime string RunRoot string @@ -111,13 +110,6 @@ var _ = SynchronizedBeforeSuite(func() []byte { cwd, _ := os.Getwd() INTEGRATION_ROOT = filepath.Join(cwd, "../../") podman := PodmanTestSetup("/tmp") - podman.ArtifactPath = ARTIFACT_DIR - if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) { - if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil { - fmt.Printf("%q\n", err) - os.Exit(1) - } - } // Pull cirros but don't put it into the cache pullImages := []string{cirros, fedoraToolbox, volumeTest} @@ -130,7 +122,7 @@ var _ = SynchronizedBeforeSuite(func() []byte { fmt.Printf("%q\n", err) os.Exit(1) } - podman.CrioRoot = ImageCacheDir + podman.Root = ImageCacheDir // If running localized tests, the cache dir is created and populated. if the // tests are remote, this is a no-op populateCache(podman) @@ -170,7 +162,6 @@ var _ = SynchronizedBeforeSuite(func() []byte { func (p *PodmanTestIntegration) Setup() { cwd, _ := os.Getwd() INTEGRATION_ROOT = filepath.Join(cwd, "../../") - p.ArtifactPath = ARTIFACT_DIR } var _ = SynchronizedAfterSuite(func() {}, @@ -181,14 +172,14 @@ var _ = SynchronizedAfterSuite(func() {}, fmt.Printf("%s\t\t%f\n", result.name, result.length) } - // previous crio-run + // previous runroot tempdir, err := CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest := PodmanTestCreate(tempdir) - if err := os.RemoveAll(podmanTest.CrioRoot); err != nil { + if err := os.RemoveAll(podmanTest.Root); err != nil { fmt.Printf("%q\n", err) } @@ -265,18 +256,17 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { PodmanTest: PodmanTest{ PodmanBinary: podmanBinary, RemotePodmanBinary: podmanRemoteBinary, - ArtifactPath: ARTIFACT_DIR, TempDir: tempDir, RemoteTest: remote, ImageCacheFS: storageFs, ImageCacheDir: ImageCacheDir, }, ConmonBinary: conmonBinary, - CrioRoot: filepath.Join(tempDir, "crio"), + Root: filepath.Join(tempDir, "root"), TmpDir: tempDir, CNIConfigDir: CNIConfigDir, OCIRuntime: ociRuntime, - RunRoot: filepath.Join(tempDir, "crio-run"), + RunRoot: filepath.Join(tempDir, "runroot"), StorageOptions: storageOptions, SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"), CgroupManager: cgroupManager, @@ -308,15 +298,29 @@ func (p PodmanTestIntegration) AddImageToRWStore(image string) { } } -// createArtifact creates a cached image in the artifact dir +func imageTarPath(image string) string { + cacheDir := os.Getenv("PODMAN_TEST_IMAGE_CACHE_DIR") + if cacheDir == "" { + cacheDir = os.Getenv("TMPDIR") + if cacheDir == "" { + cacheDir = "/tmp" + } + } + + // e.g., registry.com/fubar:latest -> registry.com-fubar-latest.tar + imageCacheName := strings.Replace(strings.Replace(image, ":", "-", -1), "/", "-", -1) + ".tar" + + return filepath.Join(cacheDir, imageCacheName) +} + +// createArtifact creates a cached image tarball in a local directory func (p *PodmanTestIntegration) createArtifact(image string) { if os.Getenv("NO_TEST_CACHE") != "" { return } - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - fmt.Printf("Caching %s at %s...\n", image, destName) + destName := imageTarPath(image) if _, err := os.Stat(destName); os.IsNotExist(err) { + fmt.Printf("Caching %s at %s...\n", image, destName) pull := p.PodmanNoCache([]string{"pull", image}) pull.Wait(440) Expect(pull).Should(Exit(0)) @@ -326,7 +330,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) { Expect(save).Should(Exit(0)) fmt.Printf("\n") } else { - fmt.Printf(" already exists.\n") + fmt.Printf("[image already cached: %s]\n", destName) } } @@ -738,12 +742,13 @@ func (p *PodmanTestIntegration) RestartRemoteService() { // RestoreArtifactToCache populates the imagecache from tarballs that were cached earlier func (p *PodmanTestIntegration) RestoreArtifactToCache(image string) error { - fmt.Printf("Restoring %s...\n", image) - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - p.CrioRoot = p.ImageCacheDir - restore := p.PodmanNoEvents([]string{"load", "-q", "-i", destName}) - restore.WaitWithDefaultTimeout() + tarball := imageTarPath(image) + if _, err := os.Stat(tarball); err == nil { + fmt.Printf("Restoring %s...\n", image) + p.Root = p.ImageCacheDir + restore := p.PodmanNoEvents([]string{"load", "-q", "-i", tarball}) + restore.WaitWithDefaultTimeout() + } return nil } @@ -795,7 +800,7 @@ func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache boo } podmanOptions := strings.Split(fmt.Sprintf("%s--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s --tmpdir %s --events-backend %s", - debug, p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager, p.TmpDir, eventsType), " ") + debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager, p.TmpDir, eventsType), " ") if os.Getenv("HOOK_OPTION") != "" { podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) } diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 6bc230aae..2a6f177f2 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -245,7 +245,7 @@ var _ = Describe("Podman run", func() { session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputStartsWith("search")).To(BeFalse()) + Expect(session.OutputToStringArray()).To(Not(ContainElement(HavePrefix("search")))) }) It("podman run use containers.conf search domain", func() { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 52d439f4a..321361382 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -107,9 +107,7 @@ var _ = Describe("Podman create", func() { check := podmanTest.Podman([]string{"inspect", "annotate_test"}) check.WaitWithDefaultTimeout() data := check.InspectContainerToJSON() - value, ok := data[0].Config.Annotations["HELLO"] - Expect(ok).To(BeTrue(), ".Config.Annotations[HELLO]") - Expect(value).To(Equal("WORLD")) + Expect(data[0].Config.Annotations).To(HaveKeyWithValue("HELLO", "WORLD")) }) It("podman create --entrypoint command", func() { @@ -386,10 +384,8 @@ var _ = Describe("Podman create", func() { data := inspect.InspectContainerToJSON() Expect(len(data)).To(Equal(1), "len(InspectContainerToJSON)") Expect(len(data[0].Config.Labels)).To(Equal(2)) - _, ok1 := data[0].Config.Labels["TESTKEY1"] - Expect(ok1).To(BeTrue(), ".Config.Labels[TESTKEY1]") - _, ok2 := data[0].Config.Labels["TESTKEY2"] - Expect(ok2).To(BeTrue(), ".Config.Labels[TESTKEY2]") + Expect(data[0].Config.Labels).To(HaveKey("TESTKEY1")) + Expect(data[0].Config.Labels).To(HaveKey("TESTKEY2")) }) It("podman create with set label", func() { @@ -404,12 +400,8 @@ var _ = Describe("Podman create", func() { data := inspect.InspectContainerToJSON() Expect(len(data)).To(Equal(1)) Expect(len(data[0].Config.Labels)).To(Equal(2)) - val1, ok1 := data[0].Config.Labels["TESTKEY1"] - Expect(ok1).To(BeTrue(), ".Config.Labels[TESTKEY1]") - Expect(val1).To(Equal("value1")) - val2, ok2 := data[0].Config.Labels["TESTKEY2"] - Expect(ok2).To(BeTrue(), ".Config.Labels[TESTKEY2]") - Expect(val2).To(Equal("bar")) + Expect(data[0].Config.Labels).To(HaveKeyWithValue("TESTKEY1", "value1")) + Expect(data[0].Config.Labels).To(HaveKeyWithValue("TESTKEY2", "bar")) }) It("podman create with --restart=on-failure:5 parses correctly", func() { diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 62482598a..735334eec 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -606,9 +606,7 @@ var _ = Describe("Podman generate kube", func() { pod := new(v1.Pod) err = yaml.Unmarshal(b, pod) Expect(err).To(BeNil()) - val, found := pod.Annotations[define.BindMountPrefix+vol1] - Expect(found).To(BeTrue(), "pod.Annotations["+vol1+"]") - Expect(val).To(HaveSuffix("z")) + Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix+vol1, HaveSuffix("z"))) rm := podmanTest.Podman([]string{"pod", "rm", "-t", "0", "-f", "test1"}) rm.WaitWithDefaultTimeout() @@ -1071,9 +1069,7 @@ USER test1` err := yaml.Unmarshal(kube.Out.Contents(), pod) Expect(err).To(BeNil()) - v, ok := pod.GetAnnotations()["io.containers.autoupdate/top"] - Expect(ok).To(Equal(true)) - Expect(v).To(Equal("local")) + Expect(pod.GetAnnotations()).To(HaveKeyWithValue("io.containers.autoupdate/top", "local")) }) It("podman generate kube on pod with auto update labels in all containers", func() { @@ -1100,13 +1096,8 @@ USER test1` Expect(pod.Spec.Containers[1].WorkingDir).To(Equal("/root")) for _, ctr := range []string{"top1", "top2"} { - v, ok := pod.GetAnnotations()["io.containers.autoupdate/"+ctr] - Expect(ok).To(Equal(true)) - Expect(v).To(Equal("registry")) - - v, ok = pod.GetAnnotations()["io.containers.autoupdate.authfile/"+ctr] - Expect(ok).To(Equal(true)) - Expect(v).To(Equal("/some/authfile.json")) + Expect(pod.GetAnnotations()).To(HaveKeyWithValue("io.containers.autoupdate/"+ctr, "registry")) + Expect(pod.GetAnnotations()).To(HaveKeyWithValue("io.containers.autoupdate.authfile/"+ctr, "/some/authfile.json")) } }) }) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 8e21ca606..9cc04e6b0 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -229,11 +229,10 @@ WORKDIR /test result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) - found, _ := result.GrepString("<none>") if noneTag { - Expect(found).To(BeTrue()) + Expect(result.OutputToString()).To(ContainSubstring("<none>")) } else { - Expect(found).To(BeFalse()) + Expect(result.OutputToString()).To(Not(ContainSubstring("<none>"))) } } // No "<none>" tag as tagged alpine instances should be present. diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go index 2ecbd0eab..d60383029 100644 --- a/test/e2e/libpod_suite_remote_test.go +++ b/test/e2e/libpod_suite_remote_test.go @@ -148,7 +148,7 @@ func (p *PodmanTestIntegration) StopRemoteService() { //MakeOptions assembles all the podman main options func getRemoteOptions(p *PodmanTestIntegration, args []string) []string { podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", - p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") + p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") if os.Getenv("HOOK_OPTION") != "" { podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) } @@ -164,15 +164,16 @@ func (p *PodmanTestIntegration) SeedImages() error { // RestoreArtifact puts the cached image into our test store func (p *PodmanTestIntegration) RestoreArtifact(image string) error { - fmt.Printf("Restoring %s...\n", image) - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - args := []string{"load", "-q", "-i", destName} - podmanOptions := getRemoteOptions(p, args) - command := exec.Command(p.PodmanBinary, podmanOptions...) - fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) - command.Start() - command.Wait() + tarball := imageTarPath(image) + if _, err := os.Stat(tarball); err == nil { + fmt.Printf("Restoring %s...\n", image) + args := []string{"load", "-q", "-i", tarball} + podmanOptions := getRemoteOptions(p, args) + command := exec.Command(p.PodmanBinary, podmanOptions...) + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + command.Start() + command.Wait() + } return nil } diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 001a869b1..4147ba2c3 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "github.com/containers/podman/v3/pkg/rootless" ) @@ -59,11 +58,12 @@ func PodmanTestCreate(tempDir string) *PodmanTestIntegration { // RestoreArtifact puts the cached image into our test store func (p *PodmanTestIntegration) RestoreArtifact(image string) error { - fmt.Printf("Restoring %s...\n", image) - dest := strings.Split(image, "/") - destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1)) - restore := p.PodmanNoEvents([]string{"load", "-q", "-i", destName}) - restore.Wait(90) + tarball := imageTarPath(image) + if _, err := os.Stat(tarball); err == nil { + fmt.Printf("Restoring %s...\n", image) + restore := p.PodmanNoEvents([]string{"load", "-q", "-i", tarball}) + restore.Wait(90) + } return nil } diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index ad5634e0c..ac70ebd8c 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -160,8 +160,7 @@ var _ = Describe("Podman load", func() { Expect(result).Should(Exit(125)) errMsg := fmt.Sprintf("remote client supports archives only but %q is a directory", podmanTest.TempDir) - found, _ := result.ErrorGrepString(errMsg) - Expect(found).Should(BeTrue()) + Expect(result.ErrorToString()).To(ContainSubstring(errMsg)) }) It("podman load bogus file", func() { diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index ff306f0f8..d56cde9db 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -140,7 +140,7 @@ var _ = Describe("Podman network", func() { session := podmanTest.Podman([]string{"network", "ls", "--filter", "label=abc"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains(name)).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring(name))) }) It("podman network ID test", func() { @@ -208,7 +208,7 @@ var _ = Describe("Podman network", func() { results := podmanTest.Podman([]string{"network", "ls", "--quiet"}) results.WaitWithDefaultTimeout() Expect(results).Should(Exit(0)) - Expect(results.LineInOutputContains(name)).To(BeFalse()) + Expect(results.OutputToString()).To(Not(ContainSubstring(name))) }) } diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 223fcc5b2..6b0081171 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -98,8 +98,7 @@ var _ = Describe("Podman prune", func() { session := podmanTest.Podman([]string{"images", "-a"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - hasNone, _ := session.GrepString("<none>") - Expect(hasNone).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("<none>"))) numImages := len(session.OutputToStringArray()) // Since there's no dangling image, none should be removed. @@ -125,8 +124,7 @@ var _ = Describe("Podman prune", func() { session = podmanTest.Podman([]string{"images", "-a"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - hasNone, _ = session.GrepString("<none>") - Expect(hasNone).To(BeTrue()) // ! we have dangling ones + Expect(session.OutputToString()).To(ContainSubstring("<none>")) numImages = len(session.OutputToStringArray()) // Since there's at least one dangling image, prune should @@ -135,7 +133,7 @@ var _ = Describe("Podman prune", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) numPrunedImages := len(session.OutputToStringArray()) - Expect(numPrunedImages >= 1).To(BeTrue()) + Expect(numPrunedImages).To(BeNumerically(">=", 1), "numPrunedImages") // Now make sure that exactly the number of pruned images has // been removed. @@ -189,11 +187,11 @@ var _ = Describe("Podman prune", func() { after := podmanTest.Podman([]string{"images", "-a"}) after.WaitWithDefaultTimeout() - Expect(none).Should(Exit(0)) - hasNoneAfter, result := none.GrepString("<none>") + Expect(after).Should(Exit(0)) + hasNoneAfter, result := after.GrepString("<none>") Expect(hasNoneAfter).To(BeTrue()) - Expect(len(after.OutputToStringArray()) > 1).To(BeTrue()) - Expect(len(result) > 0).To(BeTrue()) + Expect(len(after.OutputToStringArray())).To(BeNumerically(">", 1)) + Expect(len(result)).To(BeNumerically(">", 0)) }) It("podman image prune unused images", func() { diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 7038a09e8..a3b5e31bb 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -2,12 +2,14 @@ package integration import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" "github.com/containers/podman/v3/pkg/rootless" . "github.com/containers/podman/v3/test/utils" + "github.com/containers/storage/pkg/archive" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -63,6 +65,36 @@ var _ = Describe("Podman push", func() { Expect(session).Should(Exit(0)) }) + It("podman push to oci with compression-format", func() { + SkipIfRemote("Remote push does not support dir transport") + bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci") + session := podmanTest.Podman([]string{"push", "--compression-format=zstd", "--remove-signatures", ALPINE, + fmt.Sprintf("oci:%s", bbdir)}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + foundZstdFile := false + + blobsDir := filepath.Join(bbdir, "blobs/sha256") + + blobs, err := ioutil.ReadDir(blobsDir) + Expect(err).To(BeNil()) + + for _, f := range blobs { + blobPath := filepath.Join(blobsDir, f.Name()) + + sourceFile, err := ioutil.ReadFile(blobPath) + Expect(err).To(BeNil()) + + compressionType := archive.DetectCompression(sourceFile) + if compressionType == archive.Zstd { + foundZstdFile = true + break + } + } + Expect(foundZstdFile).To(BeTrue()) + }) + It("podman push to local registry", func() { SkipIfRemote("Remote does not support --digestfile or --remove-signatures") if podmanTest.Host.Arch == "ppc64le" { diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 6a61c1292..f85a74f47 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -154,8 +154,8 @@ var _ = Describe("Podman restart", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) timeSince := time.Since(startTime) - Expect(timeSince < 10*time.Second).To(BeTrue()) - Expect(timeSince > 2*time.Second).To(BeTrue()) + Expect(timeSince).To(BeNumerically("<", 10*time.Second)) + Expect(timeSince).To(BeNumerically(">", 2*time.Second)) }) It("Podman restart --all", func() { diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 4f6d974fd..2c2a9688e 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -276,8 +276,7 @@ RUN find $LOCAL session := podmanTest.Podman([]string{"image", "rm"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(125)) - match, _ := session.ErrorGrepString("image name or ID must be specified") - Expect(match).To(BeTrue()) + Expect(session.ErrorToString()).To(ContainSubstring("image name or ID must be specified")) }) It("podman image rm - concurrent with shared layers", func() { diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index beb6390e0..8b6d535e5 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -44,7 +44,7 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--dns-search=.", ALPINE, "cat", "/etc/resolv.conf"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputStartsWith("search")).To(BeFalse()) + Expect(session.OutputToStringArray()).To(Not(ContainElement(HavePrefix("search")))) }) It("podman run add bad dns server", func() { diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index f500a3c7c..29f76bad1 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -129,6 +129,6 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"] session := podmanTest.Podman([]string{"run", "--entrypoint=uname", "foobar.com/entrypoint:latest", "-r"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputStartsWith("Linux")).To(BeFalse()) + Expect(session.OutputToStringArray()).To(Not(ContainElement(HavePrefix("Linux")))) }) }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index c0840c83b..22ec27346 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -551,8 +551,7 @@ EXPOSE 2004-2005/tcp`, ALPINE) session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOSTNAME"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - match, _ := session.GrepString(hostname) - Expect(match).Should(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring(hostname))) }) It("podman run --net host hostname test", func() { @@ -866,7 +865,6 @@ EXPOSE 2004-2005/tcp`, ALPINE) inspectOut := podmanTest.InspectContainer(ctrName) Expect(len(inspectOut)).To(Equal(1)) Expect(len(inspectOut[0].NetworkSettings.Networks)).To(Equal(1)) - _, ok := inspectOut[0].NetworkSettings.Networks["podman"] - Expect(ok).To(BeTrue()) + Expect(inspectOut[0].NetworkSettings.Networks).To(HaveKey("podman")) }) }) diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index 05cdc7d80..6d1d26914 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -38,20 +38,20 @@ var _ = Describe("Podman run passwd", func() { session := podmanTest.Podman([]string{"run", "--read-only", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("passwd"))) }) It("podman run user specified in container", func() { session := podmanTest.Podman([]string{"run", "--read-only", "-u", "bin", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("passwd"))) }) It("podman run UID specified in container", func() { session := podmanTest.Podman([]string{"run", "--read-only", "-u", "2:1", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("passwd")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("passwd"))) }) It("podman run UID not specified in container", func() { @@ -77,14 +77,14 @@ USER 1000`, ALPINE) session := podmanTest.Podman([]string{"run", "--read-only", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("/etc/group")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("/etc/group"))) }) It("podman run group specified in container", func() { session := podmanTest.Podman([]string{"run", "--read-only", "-u", "root:bin", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("/etc/group")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("/etc/group"))) }) It("podman run non-numeric group not specified in container", func() { @@ -97,7 +97,7 @@ USER 1000`, ALPINE) session := podmanTest.Podman([]string{"run", "--read-only", "-u", "root:11", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("/etc/group")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("/etc/group"))) }) It("podman run numeric group not specified in container", func() { diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index 321bf27ac..f1084a3d2 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -63,9 +63,7 @@ var _ = Describe("Podman privileged container tests", func() { session := podmanTest.Podman([]string{"run", "--privileged", BB, "mount"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - ok, lines := session.GrepString("sysfs") - Expect(ok).To(BeTrue()) - Expect(lines[0]).To(ContainSubstring("sysfs (rw,")) + Expect(session.OutputToString()).To(ContainSubstring("sysfs (rw,")) }) It("podman privileged CapEff", func() { diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go index 49f456366..6bb325155 100644 --- a/test/e2e/run_signal_test.go +++ b/test/e2e/run_signal_test.go @@ -116,8 +116,7 @@ var _ = Describe("Podman run with --sig-proxy", func() { } session, pid := podmanTest.PodmanPID([]string{"run", "--name", "test2", "--sig-proxy=false", fedoraMinimal, "bash", "-c", sigCatch2}) - ok := WaitForContainer(podmanTest) - Expect(ok).To(BeTrue()) + Expect(WaitForContainer(podmanTest)).To(BeTrue(), "WaitForContainer()") // Kill with given signal // Should be no output, SIGPOLL is usually ignored @@ -132,8 +131,7 @@ var _ = Describe("Podman run with --sig-proxy", func() { session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) - ok, _ = session.GrepString("Received") - Expect(ok).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("Received"))) }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 6c47e9179..3d4c1240e 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -871,7 +871,7 @@ USER bin`, BB) session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.LineInOutputContains("27(video),777,65533(nogroup)")).To(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("27(video),777,65533(nogroup)"))) }) It("podman run with group-add", func() { @@ -1151,8 +1151,7 @@ USER mail`, BB) session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:z", "--volume", vol2 + ":/myvol2:z", fedoraMinimal, "findmnt", "-o", "TARGET,PROPAGATION"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - match, _ := session.GrepString("shared") - Expect(match).Should(BeFalse()) + Expect(session.OutputToString()).To(Not(ContainSubstring("shared"))) }) It("podman run findmnt shared", func() { diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 10c191bd9..74aa69c96 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -103,10 +103,8 @@ var _ = Describe("Podman run with volumes", func() { session = podmanTest.Podman([]string{"run", "--rm", "--mount", mount + ",consistency=delegated,shared", ALPINE, "grep", dest, "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - found, matches := session.GrepString(dest) - Expect(found).Should(BeTrue()) - Expect(matches[0]).To(ContainSubstring("rw")) - Expect(matches[0]).To(ContainSubstring("shared")) + Expect(session.OutputToString()).To(ContainSubstring("rw")) + Expect(session.OutputToString()).To(ContainSubstring("shared")) session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=" + dest, ALPINE, "grep", dest, "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() @@ -195,20 +193,18 @@ var _ = Describe("Podman run with volumes", func() { session := podmanTest.Podman([]string{"run", "--rm", "-v", mountPath + ":" + dest + ":suid,dev,exec", ALPINE, "grep", dest, "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - found, matches := session.GrepString(dest) - Expect(found).Should(BeTrue()) - Expect(matches[0]).To(Not(ContainSubstring("noexec"))) - Expect(matches[0]).To(Not(ContainSubstring("nodev"))) - Expect(matches[0]).To(Not(ContainSubstring("nosuid"))) + output := session.OutputToString() + Expect(output).To(Not(ContainSubstring("noexec"))) + Expect(output).To(Not(ContainSubstring("nodev"))) + Expect(output).To(Not(ContainSubstring("nosuid"))) session = podmanTest.Podman([]string{"run", "--rm", "--tmpfs", dest + ":suid,dev,exec", ALPINE, "grep", dest, "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - found, matches = session.GrepString(dest) - Expect(found).Should(BeTrue()) - Expect(matches[0]).To(Not(ContainSubstring("noexec"))) - Expect(matches[0]).To(Not(ContainSubstring("nodev"))) - Expect(matches[0]).To(Not(ContainSubstring("nosuid"))) + output = session.OutputToString() + Expect(output).To(Not(ContainSubstring("noexec"))) + Expect(output).To(Not(ContainSubstring("nodev"))) + Expect(output).To(Not(ContainSubstring("nosuid"))) }) // Container should start when workdir is overlay volume diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 54bc5252e..c67ef7ed2 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "os" - "regexp" "strconv" "text/template" @@ -107,10 +106,8 @@ registries = ['{{.Host}}:{{.Port}}']` search.WaitWithDefaultTimeout() Expect(search).Should(Exit(0)) output := string(search.Out.Contents()) - match, _ := regexp.MatchString(`(?m)NAME\s+DESCRIPTION$`, output) - Expect(match).To(BeTrue()) - match, _ = regexp.MatchString(`(?m)quay.io/libpod/whalesay\s+Static image used for automated testing.+$`, output) - Expect(match).To(BeTrue()) + Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION$`)) + Expect(output).To(MatchRegexp(`(?m)quay.io/libpod/whalesay\s+Static image used for automated testing.+$`)) }) It("podman search image with --compatible", func() { @@ -118,8 +115,7 @@ registries = ['{{.Host}}:{{.Port}}']` search.WaitWithDefaultTimeout() Expect(search).Should(Exit(0)) output := string(search.Out.Contents()) - match, _ := regexp.MatchString(`(?m)NAME\s+DESCRIPTION\s+STARS\s+OFFICIAL\s+AUTOMATED$`, output) - Expect(match).To(BeTrue()) + Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION\s+STARS\s+OFFICIAL\s+AUTOMATED$`)) }) It("podman search format flag", func() { @@ -354,8 +350,7 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search).Should(Exit(125)) Expect(search.OutputToString()).Should(BeEmpty()) - match, _ := search.ErrorGrepString("error") - Expect(match).Should(BeTrue()) + Expect(search.ErrorToString()).To(ContainSubstring("error")) // cleanup resetRegistriesConfigEnv() @@ -397,8 +392,7 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search).Should(Exit(125)) Expect(search.OutputToString()).Should(BeEmpty()) - match, _ := search.ErrorGrepString("error") - Expect(match).Should(BeTrue()) + Expect(search.ErrorToString()).To(ContainSubstring("error")) // cleanup resetRegistriesConfigEnv() @@ -451,8 +445,7 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search).Should(Exit(125)) Expect(search.OutputToString()).Should(BeEmpty()) - match, _ := search.ErrorGrepString("error") - Expect(match).Should(BeTrue()) + Expect(search.ErrorToString()).To(ContainSubstring("error")) // cleanup resetRegistriesConfigEnv() @@ -474,7 +467,7 @@ registries = ['{{.Host}}:{{.Port}}']` search = podmanTest.Podman([]string{"search", "registry.redhat.io/*openshift*"}) search.WaitWithDefaultTimeout() Expect(search).Should(Exit(0)) - Expect(len(search.OutputToStringArray()) > 1).To(BeTrue()) + Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1)) }) It("podman search repository tags", func() { @@ -494,7 +487,7 @@ registries = ['{{.Host}}:{{.Port}}']` search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/"}) search.WaitWithDefaultTimeout() - Expect(len(search.OutputToStringArray()) == 0).To(BeTrue()) + Expect(search.OutputToStringArray()).To(BeEmpty()) }) It("podman search with limit over 100", func() { diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index 93c4f3f12..1a71ebf33 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -101,6 +101,11 @@ var _ = Describe("Podman top", func() { result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) + + result = podmanTest.Podman([]string{"top", session.OutputToString(), "ax -o args"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(result.OutputToStringArray()).To(Equal([]string{"COMMAND", "top -d 2"})) }) It("podman top with comma-separated options", func() { diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 0119e0f7a..a05c1b593 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -127,6 +127,6 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "ls"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(len(session.OutputToStringArray()) >= 2).To(BeTrue()) + Expect(len(session.OutputToStringArray())).To(BeNumerically(">=", 2)) }) }) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 1e9d5f181..9de31f96c 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -221,9 +221,7 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z iid=${output:0:12} # Run the test: this will output three column-aligned rows. Test them. - # Tab character (\t) should have the same effect as the 'table' directive _run_format_test 'table' 'table {{.Repository}} {{.Tag}} {{.ID}}' - _run_format_test 'tabs' '{{.Repository}}\t{{.Tag}}\t{{.ID}}' # Clean up. run_podman rmi ${aaa_name}:${aaa_tag} ${zzz_name}:${zzz_tag} diff --git a/test/system/110-history.bats b/test/system/110-history.bats index 75c15b088..0f6d75cb3 100644 --- a/test/system/110-history.bats +++ b/test/system/110-history.bats @@ -21,6 +21,14 @@ load helpers done } +@test "podman history - custom format" { + run_podman history --format "{{.ID}}\t{{.ID}}" $IMAGE + od -c <<<$output + while IFS= read -r row; do + is "$row" ".* .*$" + done <<<$output +} + @test "podman history - json" { # Sigh. Timestamp in .created can be '...Z' or '...-06:00' tests=" diff --git a/test/system/180-blkio.bats b/test/system/180-blkio.bats new file mode 100644 index 000000000..68449681a --- /dev/null +++ b/test/system/180-blkio.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats -*- bats -*- +# +# podman blkio-related tests +# + +load helpers + +function teardown() { + lofile=${PODMAN_TMPDIR}/disk.img + if [ -f ${lofile} ]; then + run_podman '?' rm -t 0 --all --force + + while read path dev; do + if [[ "$path" == "$lofile" ]]; then + losetup -d $dev + fi + done < <(losetup -l --noheadings --output BACK-FILE,NAME) + + rm ${lofile} + fi + basic_teardown +} + +@test "podman run --blkio-weight-device" { + + skip_if_rootless "cannot create devices in rootless mode" + + # create loopback device + lofile=${PODMAN_TMPDIR}/disk.img + fallocate -l 1k ${lofile} + losetup -f ${lofile} + + run losetup -l --noheadings --output BACK-FILE,NAME,MAJ:MIN + is "$output" ".\+" "Empty output from losetup" + + lodevice=$(awk "\$1 == \"$lofile\" { print \$2 }" <<<"$output") + lomajmin=$(awk "\$1 == \"$lofile\" { print \$3 }" <<<"$output") + + is "$lodevice" ".\+" "Could not determine device for $lofile" + is "$lomajmin" ".\+" "Could not determine major/minor for $lofile" + + # use bfq io scheduler + run grep -w bfq /sys/block/$(basename ${lodevice})/queue/scheduler + if [ $status -ne 0 ]; then + skip "BFQ scheduler is not supported on the system" + fi + echo bfq > /sys/block/$(basename ${lodevice})/queue/scheduler + + # run podman + if is_cgroupsv2; then + if [ ! -f /sys/fs/cgroup/system.slice/io.bfq.weight ]; then + skip "Kernel does not support BFQ IO scheduler" + fi + run_podman run --device ${lodevice}:${lodevice} --blkio-weight-device ${lodevice}:123 --rm $IMAGE \ + /bin/sh -c "cat /sys/fs/cgroup/\$(sed -e 's/0:://' < /proc/self/cgroup)/io.bfq.weight" + is "${lines[1]}" "${lomajmin}\s\+123" + else + if [ ! -f /sys/fs/cgroup/blkio/system.slice/blkio.bfq.weight_device ]; then + skip "Kernel does not support BFQ IO scheduler" + fi + if [ $(podman_runtime) = "crun" ]; then + # As of crun 1.2, crun doesn't support blkio.bfq.weight_device + skip "crun doesn't support blkio.bfq.weight_device" + fi + run_podman run --device ${lodevice}:${lodevice} --blkio-weight-device ${lodevice}:123 --rm $IMAGE \ + /bin/sh -c "cat /sys/fs/cgroup/blkio/blkio.bfq.weight_device" + is "${lines[1]}" "${lomajmin}\s\+123" + fi +} diff --git a/test/utils/utils.go b/test/utils/utils.go index 944c1ac3c..f41024072 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -36,7 +36,6 @@ type PodmanTestCommon interface { type PodmanTest struct { PodmanMakeOptions func(args []string, noEvents, noCache bool) []string PodmanBinary string - ArtifactPath string TempDir string RemoteTest bool RemotePodmanBinary string |