summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--README.md2
-rw-r--r--cmd/podman/containers/ps.go138
-rw-r--r--cmd/podman/containers/stats.go17
-rw-r--r--cmd/podman/images/history.go3
-rw-r--r--cmd/podman/images/list.go13
-rw-r--r--cmd/podman/images/search.go19
-rw-r--r--cmd/podman/networks/list.go46
-rw-r--r--cmd/podman/parse/template.go22
-rw-r--r--cmd/podman/parse/template_test.go30
-rw-r--r--cmd/podman/pods/ps.go9
-rw-r--r--cmd/podman/pods/stats.go3
-rw-r--r--cmd/podman/system/df.go28
-rw-r--r--cmd/podman/volumes/list.go5
-rwxr-xr-xcontrib/cirrus/setup_environment.sh10
-rw-r--r--docs/Readme.md5
-rw-r--r--libpod/container_internal_linux.go22
-rw-r--r--libpod/runtime_pod_infra_linux.go1
-rw-r--r--pkg/domain/infra/abi/terminal/sigproxy_linux.go8
-rw-r--r--pkg/spec/parse.go2
-rw-r--r--pkg/specgen/generate/namespaces.go1
-rw-r--r--pkg/specgen/generate/validate.go2
-rw-r--r--test/e2e/pod_pod_namespaces.go19
-rw-r--r--test/system/200-pod.bats24
-rw-r--r--test/utils/utils.go14
25 files changed, 316 insertions, 130 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
diff --git a/README.md b/README.md
index ef844ac74..18da18465 100644
--- a/README.md
+++ b/README.md
@@ -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 29f5558cf..aabcf98ff 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 b26a60146..bf74ca954 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/spec/parse.go b/pkg/spec/parse.go
index 38d93b87f..9ebcf8d29 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -173,7 +173,7 @@ func ParseDevice(device string) (string, string, string, error) { //nolint
if IsValidDeviceMode(arr[1]) {
permissions = arr[1]
} else {
- if arr[1][0] != '/' {
+ if len(arr[1]) == 0 || arr[1][0] != '/' {
return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
}
dst = arr[1]
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index a37926167..ddc73ca61 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/pkg/specgen/generate/validate.go b/pkg/specgen/generate/validate.go
index ed337321b..f7d80ff75 100644
--- a/pkg/specgen/generate/validate.go
+++ b/pkg/specgen/generate/validate.go
@@ -38,7 +38,7 @@ func verifyContainerResources(s *specgen.SpecGenerator) ([]string, error) {
memory.Swap = nil
}
if memory.Limit != nil && memory.Swap != nil && !sysInfo.SwapLimit {
- warnings = append(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.")
+ warnings = append(warnings, "Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.")
memory.Swap = nil
}
if memory.Limit != nil && memory.Swap != nil && *memory.Swap < *memory.Limit {
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, " ")))
}