diff options
-rw-r--r-- | CONTRIBUTING.md | 3 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | cmd/podman/containers/ps.go | 138 | ||||
-rw-r--r-- | cmd/podman/containers/stats.go | 17 | ||||
-rw-r--r-- | cmd/podman/images/history.go | 3 | ||||
-rw-r--r-- | cmd/podman/images/list.go | 13 | ||||
-rw-r--r-- | cmd/podman/images/search.go | 19 | ||||
-rw-r--r-- | cmd/podman/networks/list.go | 46 | ||||
-rw-r--r-- | cmd/podman/parse/template.go | 22 | ||||
-rw-r--r-- | cmd/podman/parse/template_test.go | 30 | ||||
-rw-r--r-- | cmd/podman/pods/ps.go | 9 | ||||
-rw-r--r-- | cmd/podman/pods/stats.go | 3 | ||||
-rw-r--r-- | cmd/podman/system/df.go | 28 | ||||
-rw-r--r-- | cmd/podman/volumes/list.go | 5 | ||||
-rwxr-xr-x | contrib/cirrus/setup_environment.sh | 10 | ||||
-rw-r--r-- | docs/Readme.md | 5 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 22 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/abi/terminal/sigproxy_linux.go | 8 | ||||
-rw-r--r-- | pkg/specgen/generate/namespaces.go | 1 | ||||
-rw-r--r-- | test/e2e/pod_pod_namespaces.go | 19 | ||||
-rw-r--r-- | test/system/200-pod.bats | 24 | ||||
-rw-r--r-- | test/utils/utils.go | 14 |
23 files changed, 314 insertions, 128 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a813fcc35..308c7b197 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -361,9 +361,6 @@ All pull requests and branch-merges automatically run: * Integration Testing * Special testing (like running inside a container, or as a regular user) -For a more in-depth reference of the CI system, please [refer to it's dedicated -documentation.](contrib/cirrus/README.md) - There is always additional complexity added by automation, and so it sometimes can fail for any number of reasons. This includes post-merge testing on all branches, which you may occasionally see [red bars on the status graph @@ -10,7 +10,7 @@ Podman is based on libpod, a library for container lifecycle management that is * Latest Remote client for MacOs * Latest Static Remote client for Linux -* [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/podman.svg)](https://cirrus-ci.com/github/containers/podman/master) +* Continuous Integration: [![Build Status](https://api.cirrus-ci.com/github/containers/podman.svg)](https://cirrus-ci.com/github/containers/podman/master) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/podman/libpod?status.svg)](https://godoc.org/github.com/containers/podman/libpod) ## Overview and scope diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 90f4db19c..7da430bc6 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -12,6 +12,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -40,9 +41,8 @@ var ( listOpts = entities.ContainerListOptions{ Filters: make(map[string][]string), } - filters []string - noTrunc bool - defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + filters []string + noTrunc bool ) func init() { @@ -91,10 +91,6 @@ func checkFlags(c *cobra.Command) error { if listOpts.Size || listOpts.Namespace { return errors.Errorf("quiet conflicts with size and namespace") } - if c.Flag("format").Changed && !report.IsJSON(listOpts.Format) { - // Quiet is overridden by Go template output. - listOpts.Quiet = false - } } // Size and namespace conflict with each other if listOpts.Size && listOpts.Namespace { @@ -155,7 +151,7 @@ func getResponses() ([]entities.ListContainer, error) { return responses, nil } -func ps(cmd *cobra.Command, args []string) error { +func ps(cmd *cobra.Command, _ []string) error { if err := checkFlags(cmd); err != nil { return err } @@ -180,24 +176,22 @@ func ps(cmd *cobra.Command, args []string) error { switch { case report.IsJSON(listOpts.Format): return jsonOut(listContainers) - case listOpts.Quiet: + case listOpts.Quiet && !cmd.Flags().Changed("format"): return quietOut(listContainers) } - // Output table Watch > 0 will refresh screen responses := make([]psReporter, 0, len(listContainers)) for _, r := range listContainers { responses = append(responses, psReporter{r}) } - var headers, format string + hdrs, format := createPsOut() if cmd.Flags().Changed("format") { - headers = "" format = report.NormalizeFormat(listOpts.Format) - } else { - headers, format = createPsOut() + format = parse.EnforceRange(format) } - format = headers + "{{range . }}" + format + "{{end}}" + ns := strings.NewReplacer(".Namespaces.", ".") + format = ns.Replace(format) tmpl, err := template.New("listContainers").Parse(format) if err != nil { @@ -206,13 +200,19 @@ func ps(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if listOpts.Watch > 0 { - for { - var responses []psReporter - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() + headers := func() error { return nil } + if !(listOpts.Quiet || cmd.Flags().Changed("format")) { + headers = func() error { + return tmpl.Execute(w, hdrs) + } + } + switch { + // Output table Watch > 0 will refresh screen + case listOpts.Watch > 0: + // responses will grow to the largest number of processes reported on, but will not thrash the gc + var responses []psReporter + for ; ; responses = responses[:0] { if ctnrs, err := getResponses(); err != nil { return err } else { @@ -221,18 +221,27 @@ func ps(cmd *cobra.Command, args []string) error { } } + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } if err := w.Flush(); err != nil { + // we usually do not care about Flush() failures but here do not loop if Flush() has failed return err } + time.Sleep(time.Duration(listOpts.Watch) * time.Second) - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() } - } else if listOpts.Watch < 1 { + default: + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } @@ -241,30 +250,36 @@ func ps(cmd *cobra.Command, args []string) error { } // cannot use report.Headers() as it doesn't support structures as fields -func createPsOut() (string, string) { +func createPsOut() ([]map[string]string, string) { + hdrs := report.Headers(psReporter{}, map[string]string{ + "Cgroup": "cgroupns", + "CreatedHuman": "created", + "ID": "container id", + "IPC": "ipc", + "MNT": "mnt", + "NET": "net", + "PIDNS": "pidns", + "Pod": "pod id", + "PodName": "podname", // undo camelcase space break + "UTS": "uts", + "User": "userns", + }) + var row string if listOpts.Namespace { - headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDNS\tUSERNS\tUTS\n" - row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" - return headers, row - } - headers := defaultHeaders - row += "{{.ID}}" - row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" + row = "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}" + } else { + row = "{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" - if listOpts.Pod { - headers += "\tPOD ID\tPODNAME" - row += "\t{{.Pod}}\t{{.PodName}}" - } + if listOpts.Pod { + row += "\t{{.Pod}}\t{{.PodName}}" + } - if listOpts.Size { - headers += "\tSIZE" - row += "\t{{.Size}}" + if listOpts.Size { + row += "\t{{.Size}}" + } } - - headers = report.NormalizeFormat(headers) - row = report.NormalizeFormat(row) - return headers, row + return hdrs, "{{range .}}" + row + "\n{{end}}" } type psReporter struct { @@ -367,6 +382,41 @@ func (l psReporter) CreatedHuman() string { return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" } +// Cgroup exposes .Namespaces.Cgroup +func (l psReporter) Cgroup() string { + return l.Namespaces.Cgroup +} + +// IPC exposes .Namespaces.IPC +func (l psReporter) IPC() string { + return l.Namespaces.IPC +} + +// MNT exposes .Namespaces.MNT +func (l psReporter) MNT() string { + return l.Namespaces.MNT +} + +// NET exposes .Namespaces.NET +func (l psReporter) NET() string { + return l.Namespaces.NET +} + +// PIDNS exposes .Namespaces.PIDNS +func (l psReporter) PIDNS() string { + return l.Namespaces.PIDNS +} + +// User exposes .Namespaces.User +func (l psReporter) User() string { + return l.Namespaces.User +} + +// UTS exposes .Namespaces.UTS +func (l psReporter) UTS() string { + return l.Namespaces.UTS +} + // portsToString converts the ports used to a string of the from "port1, port2" // and also groups a continuous list of ports into a readable format. func portsToString(ports []ocicni.PortMapping) string { diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 85e7a1e82..bfab469ca 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -8,6 +8,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -58,8 +59,7 @@ type statsOptionsCLI struct { } var ( - statsOptions statsOptionsCLI - defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + statsOptions statsOptionsCLI ) func statFlags(flags *pflag.FlagSet) { @@ -159,19 +159,19 @@ func outputStats(reports []define.ContainerStats) error { if report.IsJSON(statsOptions.Format) { return outputJSON(stats) } - format := defaultStatsRow - + format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" if len(statsOptions.Format) > 0 { format = report.NormalizeFormat(statsOptions.Format) - } else if len(statsOptions.Format) < 1 { - format = defaultStatsRow } - format = "{{range . }}" + format + "{{end}}" + format = parse.EnforceRange(format) + tmpl, err := template.New("stats").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + if len(statsOptions.Format) < 1 { if err := tmpl.Execute(w, headers); err != nil { return err @@ -180,9 +180,6 @@ func outputStats(reports []define.ContainerStats) error { if err := tmpl.Execute(w, stats); err != nil { return err } - if err := w.Flush(); err != nil { - return err - } return nil } diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index 3075218d1..e9751b365 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -11,6 +11,7 @@ import ( "unicode" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" @@ -119,7 +120,7 @@ func history(cmd *cobra.Command, args []string) error { case opts.quiet: row = "{{.ID}}\n" } - format := "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) tmpl, err := template.New("report").Parse(format) if err != nil { diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 489b15086..e24631b24 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/report" "github.com/containers/image/v5/docker/reference" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" @@ -105,10 +106,10 @@ func images(cmd *cobra.Command, args []string) error { return err } switch { - case listFlag.quiet: - return writeID(imgs) case report.IsJSON(listFlag.format): return writeJSON(imgs) + case listFlag.quiet: + return writeID(imgs) default: if cmd.Flag("format").Changed { listFlag.noHeading = true // V1 compatibility @@ -171,9 +172,13 @@ func writeTemplate(imgs []imageReporter) error { } else { row = report.NormalizeFormat(listFlag.format) } + format := parse.EnforceRange(row) + + tmpl, err := template.New("list").Parse(format) + if err != nil { + return err + } - format := "{{range . }}" + row + "{{end}}" - tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index b1a1442a6..66256aaa0 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -8,6 +8,7 @@ import ( "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -130,26 +131,30 @@ func imageSearch(cmd *cobra.Command, args []string) error { } hdrs := report.Headers(entities.ImageSearchReport{}, nil) - row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" - if searchOptions.ListTags { + renderHeaders := true + var row string + switch { + case searchOptions.ListTags: if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } row = "{{.Name}}\t{{.Tag}}\n" - } - if cmd.Flags().Changed("format") { + case cmd.Flags().Changed("format"): + renderHeaders = parse.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) + default: + row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" } - row = "{{range .}}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("search").Parse(row) + tmpl, err := template.New("search").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !cmd.Flags().Changed("format") { + if renderHeaders { if err := tmpl.Execute(w, hdrs); err != nil { return errors.Wrapf(err, "failed to write search column headers") } diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 532af631e..f68e4ed75 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -8,6 +8,8 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/network" @@ -30,8 +32,6 @@ var ( var ( networkListOptions entities.NetworkListOptions - headers = "NAME\tVERSION\tPLUGINS\n" - defaultListRow = "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" ) func networkListFlags(flags *pflag.FlagSet) { @@ -66,13 +66,12 @@ func networkList(cmd *cobra.Command, args []string) error { return err } - // quiet means we only print the network names - if networkListOptions.Quiet { - return quietOut(responses) - } - - if strings.ToLower(networkListOptions.Format) == "json" { + switch { + case report.IsJSON(networkListOptions.Format): return jsonOut(responses) + case networkListOptions.Quiet: + // quiet means we only print the network names + return quietOut(responses) } nlprs := make([]ListPrintReports, 0, len(responses)) @@ -80,27 +79,32 @@ func networkList(cmd *cobra.Command, args []string) error { nlprs = append(nlprs, ListPrintReports{r}) } - row := networkListOptions.Format - if len(row) < 1 { - row = defaultListRow - } - if !strings.HasSuffix(row, "\n") { - row += "\n" + headers := report.Headers(ListPrintReports{}, map[string]string{ + "CNIVersion": "version", + "Plugins": "plugins", + }) + renderHeaders := true + row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" + if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(networkListOptions.Format) + row = report.NormalizeFormat(networkListOptions.Format) } + format := parse.EnforceRange(row) - format := "{{range . }}" + row + "{{end}}" - if !cmd.Flag("format").Changed { - format = headers + format - } tmpl, err := template.New("listNetworks").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - if err := tmpl.Execute(w, nlprs); err != nil { - return err + defer w.Flush() + + if renderHeaders { + if err := tmpl.Execute(w, headers); err != nil { + return err + } + } - return w.Flush() + return tmpl.Execute(w, nlprs) } func quietOut(responses []*entities.NetworkListReport) error { diff --git a/cmd/podman/parse/template.go b/cmd/podman/parse/template.go new file mode 100644 index 000000000..0b80f1b3a --- /dev/null +++ b/cmd/podman/parse/template.go @@ -0,0 +1,22 @@ +package parse + +import ( + "regexp" + "strings" +) + +var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*}}`) + +// TODO move to github.com/containers/common/pkg/report +// EnforceRange ensures that the format string contains a range +func EnforceRange(format string) string { + if !rangeRegex.MatchString(format) { + return "{{range .}}" + format + "{{end}}" + } + return format +} + +// EnforceRange ensures that the format string contains a range +func HasTable(format string) bool { + return strings.HasPrefix(format, "table ") +} diff --git a/cmd/podman/parse/template_test.go b/cmd/podman/parse/template_test.go new file mode 100644 index 000000000..7880d9bec --- /dev/null +++ b/cmd/podman/parse/template_test.go @@ -0,0 +1,30 @@ +package parse + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnforceRange(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"{{range .}}{{.ID}}{{end}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{.ID}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{ range . }}{{ .ID }}{{ end }}", "{{ range . }}{{ .ID }}{{ end }}"}, + // EnforceRange does not verify syntax or semantics, that will happen later + {"{{range .}}{{.ID}}", "{{range .}}{{range .}}{{.ID}}{{end}}"}, + {".ID", "{{range .}}.ID{{end}}"}, + } + + for _, tc := range tests { + tc := tc + label := "TestEnforceRange_" + tc.input + t.Run(label, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, EnforceRange(tc.input)) + }) + } +} diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 688108c1a..40fc71780 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -11,6 +11,7 @@ import ( "time" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -113,20 +114,22 @@ func pods(cmd *cobra.Command, _ []string) error { "Created": "CREATED", "InfraID": "INFRA ID", }) + renderHeaders := true row := podPsFormat() if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(psInput.Format) row = report.NormalizeFormat(psInput.Format) } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("listPods").Parse(row) + tmpl, err := template.New("listPods").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !psInput.Quiet && !cmd.Flag("format").Changed { + if renderHeaders { if err := tmpl.Execute(w, headers); err != nil { return err } diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 338f13d3e..c5d1e7f07 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -10,6 +10,7 @@ import ( "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -135,7 +136,7 @@ func printFormattedPodStatsLines(headerNames []map[string]string, row string, st return nil } - row = "{{range .}}" + row + "{{end}}" + row = parse.EnforceRange(row) tmpl, err := template.New("pod stats").Parse(row) if err != nil { diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index b11167938..fbdf274fb 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -9,6 +9,7 @@ import ( "time" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -55,7 +56,7 @@ func df(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) if dfOptions.Verbose { - return printVerbose(cmd, w, reports) + return printVerbose(w, cmd, reports) } return printSummary(w, cmd, reports) } @@ -131,20 +132,16 @@ func printSummary(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.Sys "Size": "SIZE", "Reclaimable": "RECLAIMABLE", }) - row := "{{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}\n" if cmd.Flags().Changed("format") { row = report.NormalizeFormat(dfOptions.Format) } - row = "{{range . }}" + row + "{{end}}" - - return writeTemplate(cmd, w, hdrs, row, dfSummaries) + return writeTemplate(w, cmd, hdrs, row, dfSummaries) } -func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.SystemDfReport) error { +func printVerbose(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { defer w.Flush() - // Images fmt.Fprint(w, "Images space usage:\n\n") // convert to dfImage for output dfImages := make([]*dfImage, 0, len(reports.Images)) @@ -157,14 +154,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "UniqueSize": "UNIQUE SIZE", }) imageRow := "{{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}\n" - format := "{{range . }}" + imageRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfImages); err != nil { + if err := writeTemplate(w, cmd, hdrs, imageRow, dfImages); err != nil { return nil } - // Containers fmt.Fprint(w, "\nContainers space usage:\n\n") - // convert to dfContainers for output dfContainers := make([]*dfContainer, 0, len(reports.Containers)) for _, d := range reports.Containers { @@ -176,14 +170,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "RWSize": "SIZE", }) containerRow := "{{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.RWSize}}\t{{.Created}}\t{{.Status}}\t{{.Names}}\n" - format = "{{range . }}" + containerRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfContainers); err != nil { + if err := writeTemplate(w, cmd, hdrs, containerRow, dfContainers); err != nil { return nil } - // Volumes fmt.Fprint(w, "\nLocal Volumes space usage:\n\n") - dfVolumes := make([]*dfVolume, 0, len(reports.Volumes)) // convert to dfVolume for output for _, d := range reports.Volumes { @@ -193,14 +184,13 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "VolumeName": "VOLUME NAME", }) volumeRow := "{{.VolumeName}}\t{{.Links}}\t{{.Size}}\n" - format = "{{range . }}" + volumeRow + "{{end}}" - return writeTemplate(cmd, w, hdrs, format, dfVolumes) + return writeTemplate(w, cmd, hdrs, volumeRow, dfVolumes) } -func writeTemplate(cmd *cobra.Command, w *tabwriter.Writer, hdrs []map[string]string, format string, - output interface{}) error { +func writeTemplate(w *tabwriter.Writer, cmd *cobra.Command, hdrs []map[string]string, format string, output interface{}) error { defer w.Flush() + format = parse.EnforceRange(format) tmpl, err := template.New("df").Parse(format) if err != nil { return err diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index b3b2b8ea1..ce0b7997d 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -9,6 +9,7 @@ import ( "text/template" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -91,9 +92,9 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) if cliOpts.Quiet { row = "{{.Name}}\n" } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("list volume").Parse(row) + tmpl, err := template.New("list volume").Parse(format) if err != nil { return err } diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index a3840d7e6..8ccbd95d9 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -85,6 +85,16 @@ case "$CG_FS_TYPE" in *) die_unknown CG_FS_TYPE esac +if ((CONTAINER==0)); then # Not yet running inside a container + # Discovered reemergence of BFQ scheduler bug in kernel 5.8.12-200 + # which causes a kernel panic when system is under heavy I/O load. + # Previously discovered in F32beta and confirmed fixed. It's been + # observed in F31 kernels as well. Deploy workaround for all VMs + # to ensure a more stable I/O scheduler (elevator). + echo "mq-deadline" > /sys/block/sda/queue/scheduler + warn "I/O scheduler: $(cat /sys/block/sda/queue/scheduler)" +fi + # Which distribution are we testing on. case "$OS_RELEASE_ID" in ubuntu*) ;; diff --git a/docs/Readme.md b/docs/Readme.md index 12b78d48f..c517052b3 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -50,6 +50,5 @@ the following: If reloading the page, or clearing your local cache does not fix the problem, it is likely caused by broken metadata needed to protect clients from cross-site-scripting style attacks. Please [notify a maintainer](https://github.com/containers/podman#communications) -so they may investigate how/why the swagger.yaml file's CORS-metadata is incorrect. See -[the Cirrus-CI tasks documentation](../contrib/cirrus/README.md#docs-task) for -details regarding this situation. +so they may investigate how/why the `swagger.yaml` file's CORS-metadata is +incorrect, or the file isn't accessable for some other reason. diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 57d5100cf..043924166 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -698,11 +698,31 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro } g.AddMount(systemdMnt) } else { + mountOptions := []string{"bind", "rprivate"} + + var statfs unix.Statfs_t + if err := unix.Statfs("/sys/fs/cgroup/systemd", &statfs); err != nil { + mountOptions = append(mountOptions, "nodev", "noexec", "nosuid") + } else { + if statfs.Flags&unix.MS_NODEV == unix.MS_NODEV { + mountOptions = append(mountOptions, "nodev") + } + if statfs.Flags&unix.MS_NOEXEC == unix.MS_NOEXEC { + mountOptions = append(mountOptions, "noexec") + } + if statfs.Flags&unix.MS_NOSUID == unix.MS_NOSUID { + mountOptions = append(mountOptions, "nosuid") + } + if statfs.Flags&unix.MS_RDONLY == unix.MS_RDONLY { + mountOptions = append(mountOptions, "ro") + } + } + systemdMnt := spec.Mount{ Destination: "/sys/fs/cgroup/systemd", Type: "bind", Source: "/sys/fs/cgroup/systemd", - Options: []string{"bind", "nodev", "noexec", "nosuid", "rprivate"}, + Options: mountOptions, } g.AddMount(systemdMnt) g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 7f58e86d8..76419587a 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -131,6 +131,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm logrus.Debugf("Using %q as infra container entrypoint", entryCmd) + g.RemoveMount("/dev/shm") if isRootless { g.RemoveMount("/dev/pts") devPts := spec.Mount{ diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index 0c586cf5c..2aca8f22d 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -5,8 +5,10 @@ import ( "syscall" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/signal" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -33,12 +35,16 @@ func ProxySignals(ctr *libpod.Container) { } if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { + if errors.Cause(err) == define.ErrCtrStateInvalid { + logrus.Infof("Ceasing signal forwarding to container %s as it has stopped", ctr.ID()) + } else { + logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) + } // If the container dies, and we find out here, // we need to forward that one signal to // ourselves so that it is not lost, and then // we terminate the proxy and let the defaults // play out. - logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) signal.StopCatch(sigBuffer) if err := syscall.Kill(syscall.Getpid(), s.(syscall.Signal)); err != nil { logrus.Errorf("failed to kill pid %d", syscall.Getpid()) diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 7e4f09dc4..55a0118cb 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -127,6 +127,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. return nil, errNoInfra } toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr)) + toReturn = append(toReturn, libpod.WithShmDir(infraCtr.ShmDir())) case specgen.FromContainer: ipcCtr, err := rt.LookupContainer(s.IpcNS.Value) if err != nil { diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go index 41e9c5683..20b8bdb39 100644 --- a/test/e2e/pod_pod_namespaces.go +++ b/test/e2e/pod_pod_namespaces.go @@ -60,6 +60,25 @@ var _ = Describe("Podman pod create", func() { Expect(NAMESPACE1).To(Equal(NAMESPACE2)) }) + It("podman pod container share ipc && /dev/shm ", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podID := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "start", podID}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--rm", "--pod", podID, ALPINE, "touch", "/dev/shm/test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--rm", "--pod", podID, ALPINE, "ls", "/dev/shm/test"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman pod container dontshare PIDNS", func() { session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 1d17c8cad..b0f645c53 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -116,6 +116,30 @@ function teardown() { run_podman 1 pod exists $podname } +@test "podman pod - communicating via /dev/shm " { + if is_remote && is_rootless; then + skip "FIXME: pending #7139" + fi + + podname=pod$(random_string) + run_podman 1 pod exists $podname + run_podman pod create --infra=true --name=$podname + podid="$output" + run_podman pod exists $podname + run_podman pod exists $podid + + run_podman run --rm --pod $podname $IMAGE touch /dev/shm/test1 + run_podman run --rm --pod $podname $IMAGE ls /dev/shm/test1 + is "$output" "/dev/shm/test1" + + # ...then rm the pod, then rmi the pause image so we don't leave strays. + run_podman pod rm $podname + + # Pod no longer exists + run_podman 1 pod exists $podid + run_podman 1 pod exists $podname +} + # Random byte function octet() { echo $(( $RANDOM & 255 )) diff --git a/test/utils/utils.go b/test/utils/utils.go index cb76d4a54..dd836f258 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -14,7 +14,7 @@ import ( "github.com/containers/storage/pkg/parsers/kernel" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" + . "github.com/onsi/gomega/gexec" ) var ( @@ -48,7 +48,7 @@ type PodmanTest struct { // PodmanSession wraps the gexec.session so we can extend it type PodmanSession struct { - *gexec.Session + *Session } // HostOS is a simple struct for the test os @@ -96,7 +96,7 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string command.ExtraFiles = extraFiles - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session, err := Start(command, GinkgoWriter, GinkgoWriter) if err != nil { Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err)) } @@ -125,7 +125,7 @@ func (p *PodmanTest) NumberOfContainersRunning() int { var containers []string ps := p.PodmanBase([]string{"ps", "-q"}, false, true) ps.WaitWithDefaultTimeout() - Expect(ps.ExitCode()).To(Equal(0)) + Expect(ps).Should(Exit(0)) for _, i := range ps.OutputToStringArray() { if i != "" { containers = append(containers, i) @@ -318,7 +318,7 @@ func (s *PodmanSession) IsJSONOutputValid() bool { // WaitWithDefaultTimeout waits for process finished with defaultWaitTimeout func (s *PodmanSession) WaitWithDefaultTimeout() { - Eventually(s, defaultWaitTimeout).Should(gexec.Exit()) + Eventually(s, defaultWaitTimeout).Should(Exit()) os.Stdout.Sync() os.Stderr.Sync() fmt.Println("output:", s.OutputToString()) @@ -332,7 +332,7 @@ func CreateTempDirInTempDir() (string, error) { // SystemExec is used to exec a system command to check its exit code or output func SystemExec(command string, args []string) *PodmanSession { c := exec.Command(command, args...) - session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + session, err := Start(c, GinkgoWriter, GinkgoWriter) if err != nil { Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) } @@ -343,7 +343,7 @@ func SystemExec(command string, args []string) *PodmanSession { // StartSystemExec is used to start exec a system command func StartSystemExec(command string, args []string) *PodmanSession { c := exec.Command(command, args...) - session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + session, err := Start(c, GinkgoWriter, GinkgoWriter) if err != nil { Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) } |