diff --git a/Makefile b/Makefile index a0b12e7de6..7f8f6dac65 100644 --- a/Makefile +++ b/Makefile @@ -93,11 +93,14 @@ LIBPOD := ${PROJECT}/v3/libpod GCFLAGS ?= all=-trimpath=$(CURDIR) ASMFLAGS ?= all=-trimpath=$(CURDIR) LDFLAGS_PODMAN ?= \ - -X $(LIBPOD)/define.gitCommit=$(GIT_COMMIT) \ - -X $(LIBPOD)/define.buildInfo=$(BUILD_INFO) \ - -X $(LIBPOD)/config._installPrefix=$(PREFIX) \ - -X $(LIBPOD)/config._etcDir=$(ETCDIR) \ - $(EXTRA_LDFLAGS) + -X $(LIBPOD)/define.gitCommit=$(GIT_COMMIT) \ + -X $(LIBPOD)/define.buildInfo=$(BUILD_INFO) \ + -X $(LIBPOD)/config._installPrefix=$(PREFIX) \ + -X $(LIBPOD)/config._etcDir=$(ETCDIR) \ + $(EXTRA_LDFLAGS) +LDFLAGS_PODMAN_STATIC ?= \ + $(LDFLAGS_PODMAN) \ + -extldflags=-static #Update to LIBSECCOMP_COMMIT should reflect in Dockerfile too. LIBSECCOMP_COMMIT := v2.3.3 # Rarely if ever should integration tests take more than 50min, @@ -314,7 +317,7 @@ $(SRCBINDIR)/podman$(BINSFX): $(SRCBINDIR) .gopathok $(SOURCES) go.mod go.sum -o $@ ./cmd/podman $(SRCBINDIR)/podman-remote-static: $(SRCBINDIR) .gopathok $(SOURCES) go.mod go.sum - CGO_ENABLED=$(CGO_ENABLED) \ + CGO_ENABLED=0 \ GOOS=$(GOOS) \ $(GO) build \ $(BUILDFLAGS) \ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 69c12221cd..77c717eaad 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,32 @@ # Release Notes +## 3.2.2 +### Changes +- Podman's handling of the Architecture field of images has been relaxed. Since 3.2.0, Podman required that the architecture of the image match the architecture of the system to run containers based on an image, but images often incorrectly report architecture, causing Podman to reject valid images ([#10648](https://github.com/containers/podman/issues/10648) and [#10682](https://github.com/containers/podman/issues/10682)). +- Podman no longer uses inotify to monitor for changes to CNI configurations. This removes potential issues where Podman cannot be run because a user has exhausted their available inotify sessions ([#10686](https://github.com/containers/podman/issues/10686)). + +### Bugfixes +- Fixed a bug where the `podman cp` would, when given a directory as its source and a target that existed and was a file, copy the contents of the directory into the parent directory of the file; this now results in an error. +- Fixed a bug where the `podman logs` command would, when following a running container's logs, not include the last line of output from the container when it exited when the `k8s-file` driver was in use ([#10675](https://github.com/containers/podman/issues/10675)). +- Fixed a bug where Podman would fail to run containers if `systemd-resolved` was incorrectly detected as the system's DNS server ([#10733](https://github.com/containers/podman/issues/10733)). +- Fixed a bug where the `podman exec -t` command would only resize the exec session's TTY after the session started, leading to a race condition where the terminal would initially not have a size set ([#10560](https://github.com/containers/podman/issues/10560)). +- Fixed a bug where Podman containers using the `slirp4netns` network mode would add an incorrect entry to `/etc/hosts` pointing the container's hostname to the wrong IP address. +- Fixed a bug where Podman would create volumes specified by images with incorrect permissions ([#10188](https://github.com/containers/podman/issues/10188) and [#10606](https://github.com/containers/podman/issues/10606)). +- Fixed a bug where Podman would not respect the `uid` and `gid` options to `podman volume create -o` ([#10620](https://github.com/containers/podman/issues/10620)). +- Fixed a bug where the `podman run` command could panic when parsing the system's cgroup configuration ([#10666](https://github.com/containers/podman/issues/10666)). +- Fixed a bug where the remote Podman client's `podman build -f - ...` command did not read a Containerfile from STDIN ([#10621](https://github.com/containers/podman/issues/10621)). +- Fixed a bug where the `podman container restore --import` command would fail to restore checkpoints created from privileged containers ([#10615](https://github.com/containers/podman/issues/10615)). +- Fixed a bug where Podman was not respecting the `TMPDIR` environment variable when pulling images ([#10698](https://github.com/containers/podman/issues/10698)). +- Fixed a bug where a number of Podman commands did not properly support using Go templates as an argument to the `--format` option. + +### API +- Fixed a bug where the Compat Inspect endpoint for Containers did not include information on container healthchecks ([#10457](https://github.com/containers/podman/issues/10457)). +- Fixed a bug where the Libpod and Compat Build endpoints for Images did not properly handle the `devices` query parameter ([#10614](https://github.com/containers/podman/issues/10614)). + +### Misc +- Fixed a bug where the Makefile's `make podman-remote-static` target to build a statically-linked `podman-remote` binary was instead producing dynamic binaries ([#10656](https://github.com/containers/podman/issues/10656)). +- Updated the containers/common library to v0.38.11 + ## 3.2.1 ### Changes - Podman now allows corrupt images (e.g. from restarting the system during an image pull) to be replaced by a `podman pull` of the same image (instead of requiring they be removed first, then re-pulled). diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index fd5a279d27..155d600cc8 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -3,8 +3,6 @@ package containers import ( "fmt" "os" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" @@ -118,12 +116,16 @@ func mount(_ *cobra.Command, args []string) error { mrs = append(mrs, mountReporter{r}) } - format := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}" - tmpl, err := template.New("mounts").Parse(format) + format := "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}" + tmpl, err := report.NewTemplate("mounts").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() return tmpl.Execute(w, mrs) } diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 3c01626767..ad4de68979 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -6,15 +6,12 @@ import ( "sort" "strconv" "strings" - "text/tabwriter" - "text/template" "time" tm "github.com/buger/goterm" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" "github.com/containers/podman/v3/cmd/podman/validate" @@ -228,18 +225,20 @@ func ps(cmd *cobra.Command, _ []string) error { hdrs, format := createPsOut() if cmd.Flags().Changed("format") { format = report.NormalizeFormat(listOpts.Format) - format = parse.EnforceRange(format) + format = report.EnforceRange(format) } ns := strings.NewReplacer(".Namespaces.", ".") format = ns.Replace(format) - tmpl, err := template.New("listContainers"). - Funcs(template.FuncMap(report.DefaultFuncs)). - Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() headers := func() error { return nil } @@ -322,7 +321,7 @@ func createPsOut() ([]map[string]string, string) { row += "\t{{.Size}}" } } - return hdrs, "{{range .}}" + row + "\n{{end}}" + return hdrs, "{{range .}}" + row + "\n{{end -}}" } type psReporter struct { diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 7160f1ba87..37abb70f4b 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -3,13 +3,10 @@ package containers import ( "fmt" "os" - "text/tabwriter" - "text/template" tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/libpod/define" @@ -172,13 +169,17 @@ func outputStats(reports []define.ContainerStats) error { if len(statsOptions.Format) > 0 { format = report.NormalizeFormat(statsOptions.Format) } - format = parse.EnforceRange(format) + format = report.EnforceRange(format) - tmpl, err := template.New("stats").Parse(format) + tmpl, err := report.NewTemplate("stats").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() if len(statsOptions.Format) < 1 { diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index 03cee5d564..abb3b8455e 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -5,8 +5,8 @@ import ( "fmt" "os" "strings" - "text/tabwriter" + "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" @@ -79,7 +79,7 @@ func init() { validate.AddLatestFlag(containerTopCommand, &topOptions.Latest) } -func top(cmd *cobra.Command, args []string) error { +func top(_ *cobra.Command, args []string) error { if topOptions.ListDescriptors { descriptors, err := util.GetContainerPidInformationDescriptors() if err != nil { @@ -105,7 +105,11 @@ func top(cmd *cobra.Command, args []string) error { return err } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } + for _, proc := range topResponse.Value { if _, err := fmt.Fprintln(w, proc); err != nil { return err diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index 16be0bb192..e9aa88a20b 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -5,14 +5,11 @@ import ( "fmt" "os" "strings" - "text/tabwriter" - "text/template" "time" "unicode" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/docker/go-units" @@ -127,13 +124,17 @@ func history(cmd *cobra.Command, args []string) error { case opts.quiet: row = "{{.ID}}\n" } - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("report").Parse(format) + tmpl, err := report.NewTemplate("history").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() if !opts.quiet && !cmd.Flags().Changed("format") { diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 132af858b9..e624c296be 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -5,8 +5,6 @@ import ( "os" "sort" "strings" - "text/tabwriter" - "text/template" "time" "unicode" @@ -14,7 +12,6 @@ import ( "github.com/containers/common/pkg/report" "github.com/containers/image/v5/docker/reference" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/docker/go-units" @@ -126,7 +123,7 @@ func images(cmd *cobra.Command, args []string) error { case listFlag.quiet: return writeID(imgs) default: - if cmd.Flags().Changed("format") && !parse.HasTable(listFlag.format) { + if cmd.Flags().Changed("format") && !report.HasTable(listFlag.format) { listFlag.noHeading = true } return writeTemplate(imgs) @@ -181,20 +178,23 @@ func writeTemplate(imgs []imageReporter) error { "ReadOnly": "R/O", }) - var row string + var format string if listFlag.format == "" { - row = lsFormatFromFlags(listFlag) + format = lsFormatFromFlags(listFlag) } else { - row = report.NormalizeFormat(listFlag.format) + format = report.NormalizeFormat(listFlag.format) + format = report.EnforceRange(format) } - format := parse.EnforceRange(row) - tmpl, err := template.New("list").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } defer w.Flush() if !listFlag.noHeading { @@ -323,7 +323,7 @@ func lsFormatFromFlags(flags listFlagType) string { row = append(row, "{{.ReadOnly}}") } - return strings.Join(row, "\t") + "\n" + return "{{range . }}" + strings.Join(row, "\t") + "\n{{end -}}" } type imageReporter struct { diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go index a098aac63e..3a38a7a197 100644 --- a/cmd/podman/images/mount.go +++ b/cmd/podman/images/mount.go @@ -3,8 +3,6 @@ package images import ( "fmt" "os" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" @@ -99,13 +97,18 @@ func mount(cmd *cobra.Command, args []string) error { mrs = append(mrs, mountReporter{r}) } - row := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}" - tmpl, err := template.New("mounts").Parse(row) + row := "{{range . }}{{.ID}}\t{{.Path}}\n{{end -}}" + tmpl, err := report.NewTemplate("mounts").Parse(row) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() + return tmpl.Execute(w, mrs) } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 9e7b447a4a..8dc21a5446 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -98,7 +98,7 @@ func pushFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(digestfileFlagName, completion.AutocompleteDefault) formatFlagName := "format" - flags.StringVarP(&pushOptions.Format, formatFlagName, "f", "", "Manifest type (oci, v2s2, or v2s1) to use when pushing an image using the 'dir' transport (default is manifest type of source)") + flags.StringVarP(&pushOptions.Format, formatFlagName, "f", "", "Manifest type (oci, v2s2, or v2s1) to use in the destination (default is manifest type of source, with fallbacks)") _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteManifestFormat) flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images") diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index a3cfa983f8..1bff9aed40 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -3,14 +3,11 @@ package images import ( "fmt" "os" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" @@ -163,18 +160,22 @@ func imageSearch(cmd *cobra.Command, args []string) error { case report.IsJSON(searchOptions.Format): return printArbitraryJSON(searchReport) case cmd.Flags().Changed("format"): - renderHeaders = parse.HasTable(searchOptions.Format) + renderHeaders = report.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) default: row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" } - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("search").Parse(format) + tmpl, err := report.NewTemplate("search").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() if renderHeaders { diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go index ed9aecdb7d..44ecf52f7f 100644 --- a/cmd/podman/images/trust_show.go +++ b/cmd/podman/images/trust_show.go @@ -3,9 +3,8 @@ package images import ( "fmt" "os" - "text/tabwriter" - "text/template" + "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" @@ -45,16 +44,16 @@ func init() { } func showTrust(cmd *cobra.Command, args []string) error { - report, err := registry.ImageEngine().ShowTrust(registry.Context(), args, showTrustOptions) + trust, err := registry.ImageEngine().ShowTrust(registry.Context(), args, showTrustOptions) if err != nil { return err } if showTrustOptions.Raw { - fmt.Println(string(report.Raw)) + fmt.Println(string(trust.Raw)) return nil } if showTrustOptions.JSON { - b, err := json.MarshalIndent(report.Policies, "", " ") + b, err := json.MarshalIndent(trust.Policies, "", " ") if err != nil { return err } @@ -62,14 +61,18 @@ func showTrust(cmd *cobra.Command, args []string) error { return nil } - row := "{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n" - format := "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("listContainers").Parse(format) + format := "{{range . }}{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n{{end -}}" + tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - if err := tmpl.Execute(w, report.Policies); err != nil { + + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } + + if err := tmpl.Execute(w, trust.Policies); err != nil { return err } if err := w.Flush(); err != nil { diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index 351684af1f..bd3060882e 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -7,7 +7,6 @@ import ( "os" "regexp" "strings" - "text/tabwriter" "text/template" "github.com/containers/common/pkg/completion" @@ -217,7 +216,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { err = printJSON(data) default: row := inspectNormalize(i.options.Format) - row = "{{range . }}" + report.NormalizeFormat(row) + "{{end}}" + row = "{{range . }}" + report.NormalizeFormat(row) + "{{end -}}" err = printTmpl(tmpType, row, data) } if err != nil { @@ -250,7 +249,11 @@ func printTmpl(typ, row string, data []interface{}) error { if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } return t.Execute(w, data) } diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index af4e2c807a..4d52e3f61d 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -5,14 +5,11 @@ package machine import ( "os" "sort" - "text/tabwriter" - "text/template" "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/report" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -95,16 +92,20 @@ func outputTemplate(cmd *cobra.Command, responses []*machineReporter) error { }) row := report.NormalizeFormat(listFlag.format) - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("list machines").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) defer w.Flush() - if cmd.Flags().Changed("format") && !parse.HasTable(listFlag.format) { + if cmd.Flags().Changed("format") && !report.HasTable(listFlag.format) { listFlag.noHeading = true } diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index e1b182cbf1..cbfd9363ba 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -4,8 +4,6 @@ import ( "fmt" "os" "strings" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -134,11 +132,15 @@ func templateOut(responses []*entities.NetworkListReport, cmd *cobra.Command) er } format = report.EnforceRange(row) - tmpl, err := template.New("listNetworks").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() noHeading, _ := cmd.Flags().GetBool("noheading") diff --git a/cmd/podman/parse/template.go b/cmd/podman/parse/template.go deleted file mode 100644 index 0b80f1b3ad..0000000000 --- a/cmd/podman/parse/template.go +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 7880d9bec8..0000000000 --- a/cmd/podman/parse/template_test.go +++ /dev/null @@ -1,30 +0,0 @@ -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/inspect.go b/cmd/podman/pods/inspect.go index c66b81adb4..5978673951 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -3,8 +3,6 @@ package pods import ( "context" "os" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" @@ -74,11 +72,14 @@ func inspect(cmd *cobra.Command, args []string) error { row := report.NormalizeFormat(inspectOptions.Format) - t, err := template.New("pod inspect").Parse(row) + t, err := report.NewTemplate("inspect").Parse(row) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } return t.Execute(w, *responses) } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index beaeda8717..2c8e48c1c9 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -6,14 +6,11 @@ import ( "os" "sort" "strings" - "text/tabwriter" - "text/template" "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -132,20 +129,24 @@ func pods(cmd *cobra.Command, _ []string) error { renderHeaders := true row := podPsFormat() if cmd.Flags().Changed("format") { - renderHeaders = parse.HasTable(psInput.Format) + renderHeaders = report.HasTable(psInput.Format) row = report.NormalizeFormat(psInput.Format) } noHeading, _ := cmd.Flags().GetBool("noheading") if noHeading { renderHeaders = false } - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("listPods").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() if renderHeaders { diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 97147275ea..d2dc63ae3c 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -4,14 +4,11 @@ import ( "context" "fmt" "os" - "text/tabwriter" - "text/template" "time" "github.com/buger/goterm" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -124,8 +121,12 @@ func printJSONPodStats(stats []*entities.PodStatsReport) error { return nil } -func printPodStatsLines(stats []*entities.PodStatsReport) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) +func printPodStatsLines(stats []*entities.PodStatsReport) error { + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } + outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") if len(stats) == 0 { @@ -135,7 +136,7 @@ func printPodStatsLines(stats []*entities.PodStatsReport) { fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS) } } - w.Flush() + return w.Flush() } func printFormattedPodStatsLines(headerNames []map[string]string, row string, stats []*entities.PodStatsReport) error { @@ -143,13 +144,17 @@ func printFormattedPodStatsLines(headerNames []map[string]string, row string, st return nil } - row = parse.EnforceRange(row) + row = report.EnforceRange(row) + + tmpl, err := report.NewTemplate("stats").Parse(row) + if err != nil { + return err + } - tmpl, err := template.New("pod stats").Parse(row) + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) defer w.Flush() if err := tmpl.Execute(w, headerNames); err != nil { diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index be8aab761b..f1b7a1b532 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -5,8 +5,8 @@ import ( "fmt" "os" "strings" - "text/tabwriter" + "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" @@ -83,7 +83,11 @@ func top(_ *cobra.Command, args []string) error { return err } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } + for _, proc := range topResponse.Value { if _, err := fmt.Fprintln(w, proc); err != nil { return err diff --git a/cmd/podman/secrets/inspect.go b/cmd/podman/secrets/inspect.go index bcb1adb5e0..7d44dc0757 100644 --- a/cmd/podman/secrets/inspect.go +++ b/cmd/podman/secrets/inspect.go @@ -4,13 +4,10 @@ import ( "context" "encoding/json" "fmt" - "html/template" "os" - "text/tabwriter" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" @@ -53,13 +50,17 @@ func inspect(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("format") { row := report.NormalizeFormat(format) - formatted := parse.EnforceRange(row) + formatted := report.EnforceRange(row) - tmpl, err := template.New("inspect secret").Parse(formatted) + tmpl, err := report.NewTemplate("inspect").Parse(formatted) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) defer w.Flush() tmpl.Execute(w, inspected) } else { diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index ba7065d610..29d364a1b4 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -2,15 +2,12 @@ package secrets import ( "context" - "html/template" "os" - "text/tabwriter" "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -76,16 +73,20 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.SecretListReport) }) row := report.NormalizeFormat(listFlag.format) - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("list secret").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) defer w.Flush() - if cmd.Flags().Changed("format") && !parse.HasTable(listFlag.format) { + if cmd.Flags().Changed("format") && !report.HasTable(listFlag.format) { listFlag.noHeading = true } diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go index fe7026ae37..15f0079209 100644 --- a/cmd/podman/system/connection/list.go +++ b/cmd/podman/system/connection/list.go @@ -2,11 +2,10 @@ package connection import ( "os" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/system" "github.com/containers/podman/v3/cmd/podman/validate" @@ -76,13 +75,16 @@ func list(_ *cobra.Command, _ []string) error { } // TODO: Allow user to override format - format := "{{range . }}{{.Name}}\t{{.Identity}}\t{{.URI}}\n{{end}}" - tmpl, err := template.New("connection").Parse(format) + format := "{{range . }}{{.Name}}\t{{.Identity}}\t{{.URI}}\n{{end -}}" + tmpl, err := report.NewTemplate("list").Parse(format) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } defer w.Flush() _ = tmpl.Execute(w, hdrs) diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index 5e179a82d8..f025bcc276 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -4,13 +4,10 @@ import ( "fmt" "os" "strings" - "text/tabwriter" - "text/template" "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -58,7 +55,10 @@ func df(cmd *cobra.Command, args []string) error { return err } - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } if dfOptions.Verbose { return printVerbose(w, cmd, reports) @@ -66,7 +66,7 @@ func df(cmd *cobra.Command, args []string) error { return printSummary(w, cmd, reports) } -func printSummary(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { +func printSummary(w *report.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { var ( dfSummaries []*dfSummary active int @@ -144,7 +144,7 @@ func printSummary(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.Sys return writeTemplate(w, cmd, hdrs, row, dfSummaries) } -func printVerbose(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { +func printVerbose(w *report.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { defer w.Flush() fmt.Fprint(w, "Images space usage:\n\n") @@ -192,11 +192,11 @@ func printVerbose(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.Sys return writeTemplate(w, cmd, hdrs, volumeRow, dfVolumes) } -func writeTemplate(w *tabwriter.Writer, cmd *cobra.Command, hdrs []map[string]string, format string, output interface{}) error { +func writeTemplate(w *report.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) + format = report.EnforceRange(format) + tmpl, err := report.NewTemplate("df").Parse(format) if err != nil { return err } diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 568610bdc9..c014c7fa2f 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -76,7 +75,7 @@ func eventsCmd(cmd *cobra.Command, _ []string) error { errChannel := make(chan error) var ( - tmpl *template.Template + tmpl *report.Template doJSON bool ) @@ -84,7 +83,7 @@ func eventsCmd(cmd *cobra.Command, _ []string) error { doJSON = report.IsJSON(eventFormat) if !doJSON { var err error - tmpl, err = template.New("events").Parse(eventFormat) + tmpl, err = report.NewTemplate("events").Parse(eventFormat) if err != nil { return err } diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 44be4ccec7..5917f1aae7 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -3,7 +3,6 @@ package system import ( "fmt" "os" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -88,7 +87,7 @@ func info(cmd *cobra.Command, args []string) error { } fmt.Println(string(b)) case cmd.Flags().Changed("format"): - tmpl, err := template.New("info").Parse(inFormat) + tmpl, err := report.NewTemplate("info").Parse(inFormat) if err != nil { return err } diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index ad9fd2a85a..a83539589f 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -5,8 +5,6 @@ import ( "io" "os" "strings" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" @@ -56,19 +54,22 @@ func version(cmd *cobra.Command, args []string) error { return nil } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + w, err := report.NewWriterDefault(os.Stdout) + if err != nil { + return err + } defer w.Flush() if cmd.Flag("format").Changed { row := report.NormalizeFormat(versionFormat) - tmpl, err := template.New("version 2.0.0").Parse(row) + tmpl, err := report.NewTemplate("version 2.0.0").Parse(row) if err != nil { return err } if err := tmpl.Execute(w, versions); err != nil { // On Failure, assume user is using older version of podman version --format and check client row = strings.Replace(row, ".Server.", ".", 1) - tmpl, err := template.New("version 1.0.0").Parse(row) + tmpl, err := report.NewTemplate("version 1.0.0").Parse(row) if err != nil { return err } diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index f402afa94b..621ea6089b 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -5,13 +5,10 @@ import ( "fmt" "os" "strings" - "text/tabwriter" - "text/template" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" - "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/libpod/define" @@ -105,13 +102,17 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) if cliOpts.Quiet { row = "{{.Name}}\n" } - format := parse.EnforceRange(row) + format := report.EnforceRange(row) - tmpl, err := template.New("list volume").Parse(format) + tmpl, err := report.NewTemplate("list").Parse(format) + if err != nil { + return err + } + + w, err := report.NewWriterDefault(os.Stdout) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0) defer w.Flush() if !(noHeading || cliOpts.Quiet || cmd.Flag("format").Changed) { diff --git a/go.mod b/go.mod index a6fc311644..51eee7ee5d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.1 github.com/containers/buildah v1.21.0 - github.com/containers/common v0.38.10 + github.com/containers/common v0.38.11 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.12.0 github.com/containers/ocicrypt v1.1.1 @@ -20,7 +20,7 @@ require ( github.com/containers/storage v1.31.3 github.com/coreos/go-systemd/v22 v22.3.2 github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3 - github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf + github.com/cri-o/ocicni v0.2.1-0.20210621164014-d0acc7862283 github.com/cyphar/filepath-securejoin v0.2.2 github.com/davecgh/go-spew v1.1.1 github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f diff --git a/go.sum b/go.sum index 899093c6cf..f136454a0e 100644 --- a/go.sum +++ b/go.sum @@ -218,8 +218,8 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD github.com/containers/buildah v1.21.0 h1:LuwuqRPjan3X3AIdGwfkEkqMgmrDMNpQznFqNdHgCz8= github.com/containers/buildah v1.21.0/go.mod h1:yPdlpVd93T+i91yGxrJbW1YOWrqN64j5ZhHOZmHUejs= github.com/containers/common v0.38.4/go.mod h1:egfpX/Y3+19Dz4Wa1eRZDdgzoEOeneieF9CQppKzLBg= -github.com/containers/common v0.38.10 h1:X3spMNjrqKYQ25Lc+Z1wpc0t7KIrwO6mT2S5J1TDiTI= -github.com/containers/common v0.38.10/go.mod h1:egfpX/Y3+19Dz4Wa1eRZDdgzoEOeneieF9CQppKzLBg= +github.com/containers/common v0.38.11 h1:kXgJmQCSR1rl6HtuwW3tsv6PQ9fSYpKUo693Crxoj8s= +github.com/containers/common v0.38.11/go.mod h1:egfpX/Y3+19Dz4Wa1eRZDdgzoEOeneieF9CQppKzLBg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.12.0 h1:1hNS2QkzFQ4lH3GYQLyAXB0acRMhS1Ubm6oV++8vw4w= @@ -265,8 +265,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf h1:k2wrxBiBseRfOD7h+9fABEuesABBQuUuW5fWwpARbeI= -github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY= +github.com/cri-o/ocicni v0.2.1-0.20210621164014-d0acc7862283 h1:7FyIYKksGvRF8XjMkG5T6uIxg8PcgZoPyO+f6kHT5+s= +github.com/cri-o/ocicni v0.2.1-0.20210621164014-d0acc7862283/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= diff --git a/libpod/container_exec.go b/libpod/container_exec.go index c359f1e5df..8681b4e13e 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -276,9 +276,10 @@ func (c *Container) ExecStart(sessionID string) error { } // ExecStartAndAttach starts and attaches to an exec session in a container. +// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty // TODO: Should we include detach keys in the signature to allow override? // TODO: How do we handle AttachStdin/AttachStdout/AttachStderr? -func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams) error { +func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -309,7 +310,7 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS return err } - pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams) + pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, newSize) if err != nil { return err } @@ -372,7 +373,9 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS } // ExecHTTPStartAndAttach starts and performs an HTTP attach to an exec session. -func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool) error { +// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty +func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, newSize *define.TerminalSize) error { // TODO: How do we combine streams with the default streams set in the exec session? // Ensure that we don't leak a goroutine here @@ -430,7 +433,7 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w close(holdConnOpen) }() - pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, r, w, streams, cancel, hijackDone, holdConnOpen) + pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, r, w, streams, cancel, hijackDone, holdConnOpen, newSize) if err != nil { session.State = define.ExecStateStopped session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err) @@ -733,7 +736,10 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi // API there. // TODO: Refactor so this is closed here, before we remove the exec // session. + var size *define.TerminalSize if resize != nil { + s := <-resize + size = &s go func() { logrus.Debugf("Sending resize events to exec session %s", sessionID) for resizeRequest := range resize { @@ -751,7 +757,7 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi }() } - if err := c.ExecStartAndAttach(sessionID, streams); err != nil { + if err := c.ExecStartAndAttach(sessionID, streams, size); err != nil { return -1, err } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index af7e974714..f79edfbcac 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -427,7 +427,7 @@ func (c *Container) setupStorage(ctx context.Context) error { }, LabelOpts: c.config.LabelOpts, } - if c.restoreFromCheckpoint { + if c.restoreFromCheckpoint && !c.config.Privileged { // If restoring from a checkpoint, the root file-system // needs to be mounted with the same SELinux labels as // it was mounted previously. @@ -1061,7 +1061,7 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { } for _, v := range c.config.NamedVolumes { - if err := c.chownVolume(v.Name); err != nil { + if err := c.fixVolumePermissions(v); err != nil { return err } } @@ -1531,6 +1531,16 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { }() } + // If /etc/mtab does not exist in container image, then we need to + // create it, so that mount command within the container will work. + mtab := filepath.Join(mountPoint, "/etc/mtab") + if err := idtools.MkdirAllAs(filepath.Dir(mtab), 0755, c.RootUID(), c.RootGID()); err != nil { + return "", errors.Wrap(err, "error creating mtab directory") + } + if err = os.Symlink("/proc/mounts", mtab); err != nil && !os.IsExist(err) { + return "", err + } + // Request a mount of all named volumes for _, v := range c.config.NamedVolumes { vol, err := c.mountNamedVolume(v, mountPoint) @@ -1670,64 +1680,6 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) return vol, nil } -// Chown the specified volume if necessary. -func (c *Container) chownVolume(volumeName string) error { - vol, err := c.runtime.state.Volume(volumeName) - if err != nil { - return errors.Wrapf(err, "error retrieving named volume %s for container %s", volumeName, c.ID()) - } - - vol.lock.Lock() - defer vol.lock.Unlock() - - // The volume may need a copy-up. Check the state. - if err := vol.update(); err != nil { - return err - } - - // TODO: For now, I've disabled chowning volumes owned by non-Podman - // drivers. This may be safe, but it's really going to be a case-by-case - // thing, I think - safest to leave disabled now and re-enable later if - // there is a demand. - if vol.state.NeedsChown && !vol.UsesVolumeDriver() { - vol.state.NeedsChown = false - - uid := int(c.config.Spec.Process.User.UID) - gid := int(c.config.Spec.Process.User.GID) - - if c.config.IDMappings.UIDMap != nil { - p := idtools.IDPair{ - UID: uid, - GID: gid, - } - mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap) - newPair, err := mappings.ToHost(p) - if err != nil { - return errors.Wrapf(err, "error mapping user %d:%d", uid, gid) - } - uid = newPair.UID - gid = newPair.GID - } - - vol.state.UIDChowned = uid - vol.state.GIDChowned = gid - - if err := vol.save(); err != nil { - return err - } - - mountPoint, err := vol.MountPoint() - if err != nil { - return err - } - - if err := os.Lchown(mountPoint, uid, gid); err != nil { - return err - } - } - return nil -} - // cleanupStorage unmounts and cleans up the container's root filesystem func (c *Container) cleanupStorage() error { if !c.state.Mounted { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index be59e3b54f..b31d90a6e1 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1659,9 +1659,13 @@ func (c *Container) generateResolvConf() (string, error) { // check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver if len(ns) == 1 && ns[0] == "127.0.0.53" { // read the actual resolv.conf file for systemd-resolved - contents, err = ioutil.ReadFile("/run/systemd/resolve/resolv.conf") + resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf") if err != nil { - return "", errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf") + if !os.IsNotExist(err) { + return "", errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf") + } + } else { + contents = resolvedContents } } @@ -1814,7 +1818,7 @@ func (c *Container) getHosts() string { if c.Hostname() != "" { if c.config.NetMode.IsSlirp4netns() { // When using slirp4netns, the interface gets a static IP - slirp4netnsIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) + slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) if err != nil { logrus.Warn("failed to determine slirp4netnsIP: ", err.Error()) } else { @@ -2424,3 +2428,77 @@ func (c *Container) createSecretMountDir() error { return err } + +// Fix ownership and permissions of the specified volume if necessary. +func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error { + vol, err := c.runtime.state.Volume(v.Name) + if err != nil { + return errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID()) + } + + vol.lock.Lock() + defer vol.lock.Unlock() + + // The volume may need a copy-up. Check the state. + if err := vol.update(); err != nil { + return err + } + + // TODO: For now, I've disabled chowning volumes owned by non-Podman + // drivers. This may be safe, but it's really going to be a case-by-case + // thing, I think - safest to leave disabled now and re-enable later if + // there is a demand. + if vol.state.NeedsChown && !vol.UsesVolumeDriver() { + vol.state.NeedsChown = false + + uid := int(c.config.Spec.Process.User.UID) + gid := int(c.config.Spec.Process.User.GID) + + if c.config.IDMappings.UIDMap != nil { + p := idtools.IDPair{ + UID: uid, + GID: gid, + } + mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap) + newPair, err := mappings.ToHost(p) + if err != nil { + return errors.Wrapf(err, "error mapping user %d:%d", uid, gid) + } + uid = newPair.UID + gid = newPair.GID + } + + vol.state.UIDChowned = uid + vol.state.GIDChowned = gid + + if err := vol.save(); err != nil { + return err + } + + mountPoint, err := vol.MountPoint() + if err != nil { + return err + } + + if err := os.Lchown(mountPoint, uid, gid); err != nil { + return err + } + + // Make sure the new volume matches the permissions of the target directory. + // https://github.com/containers/podman/issues/10188 + st, err := os.Lstat(filepath.Join(c.state.Mountpoint, v.Dest)) + if err == nil { + if err := os.Chmod(mountPoint, st.Mode()|0111); err != nil { + return err + } + stat := st.Sys().(*syscall.Stat_t) + atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + if err := os.Chtimes(mountPoint, atime, st.ModTime()); err != nil { + return err + } + } else if !os.IsNotExist(err) { + return err + } + } + return nil +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index f979bcbdec..125329ce58 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -57,3 +57,8 @@ func (c *Container) reloadNetwork() error { func (c *Container) getUserOverrides() *lookup.Overrides { return nil } + +// Fix ownership and permissions of the specified volume if necessary. +func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error { + return define.ErrNotImplemented +} diff --git a/libpod/diff.go b/libpod/diff.go index 6ce8d809a7..c5a53478bd 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" ) -var containerMounts = map[string]bool{ +var initInodes = map[string]bool{ "/dev": true, "/etc/hostname": true, "/etc/hosts": true, @@ -17,6 +17,7 @@ var containerMounts = map[string]bool{ "/run/.containerenv": true, "/run/secrets": true, "/sys": true, + "/etc/mtab": true, } // GetDiff returns the differences between the two images, layers, or containers @@ -36,7 +37,7 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) { changes, err := r.store.Changes(fromLayer, toLayer) if err == nil { for _, c := range changes { - if containerMounts[c.Path] { + if initInodes[c.Path] { continue } rchanges = append(rchanges, c) diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index eb515c000b..b37b150985 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -46,6 +46,9 @@ const ( // rootlessCNINSName is the file name for the rootless network namespace bind mount rootlessCNINSName = "rootless-cni-ns" + + // persistentCNIDir is the directory where the CNI files are stored + persistentCNIDir = "/var/lib/cni" ) // Get an OCICNI network config @@ -150,14 +153,31 @@ func (r *RootlessCNI) Do(toRun func() error) error { } } - // cni plugins need access to /var and /run - runDir := filepath.Join(r.dir, "run") - varDir := filepath.Join(r.dir, "var") + // cni plugins need access to /var/lib/cni and /run + varDir := "" + varTarget := persistentCNIDir + // we can only mount to a target dir which exists, check /var/lib/cni recursively + // while we could always use /var there are cases where a user might store the cni + // configs under /var/custom and this would break + for { + if _, err := os.Stat(varTarget); err == nil { + varDir = filepath.Join(r.dir, strings.TrimPrefix(varTarget, "/")) + break + } + varTarget = filepath.Base(varTarget) + if varTarget == "/" { + break + } + } + if varDir == "" { + return errors.New("failed to stat /var directory") + } // make sure to mount var first - err = unix.Mount(varDir, "/var", "none", unix.MS_BIND, "") + err = unix.Mount(varDir, varTarget, "none", unix.MS_BIND, "") if err != nil { - return errors.Wrap(err, "failed to mount /var for rootless cni") + return errors.Wrapf(err, "failed to mount %s for rootless cni", varTarget) } + runDir := filepath.Join(r.dir, "run") // recursive mount to keep the netns mount err = unix.Mount(runDir, "/run", "none", unix.MS_BIND|unix.MS_REC, "") if err != nil { @@ -386,7 +406,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { // create cni directories to store files // they will be bind mounted to the correct location in a extra mount ns - err = os.MkdirAll(filepath.Join(cniDir, "var"), 0700) + err = os.MkdirAll(filepath.Join(cniDir, strings.TrimPrefix(persistentCNIDir, "/")), 0700) if err != nil { return nil, errors.Wrap(err, "could not create rootless-cni var directory") } @@ -866,6 +886,10 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e if err != nil { return nil, err } + // see https://github.com/containers/podman/issues/10090 + // the container has to be locked for syncContainer() + netNsCtr.lock.Lock() + defer netNsCtr.lock.Unlock() // Have to sync to ensure that state is populated if err := netNsCtr.syncContainer(); err != nil { return nil, err @@ -1021,7 +1045,7 @@ func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNet // after itself on an unclean reboot. Return what we're pretty sure is the path // to CNI's internal files (it's not really exposed to us). func getCNINetworksDir() (string, error) { - return "/var/lib/cni/networks", nil + return filepath.Join(persistentCNIDir, "networks"), nil } type logrusDebugWriter struct { diff --git a/libpod/oci.go b/libpod/oci.go index 1f2c7dd711..c92d9a0778 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -72,13 +72,16 @@ type OCIRuntime interface { // has completed, as one might expect. The attach session will remain // running, in a goroutine that will return via the chan error in the // return signature. - ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) + // newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty + ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *define.TerminalSize) (int, chan error, error) // ExecContainerHTTP executes a command in a running container and // attaches its standard streams to a provided hijacked HTTP session. // Maintains the same invariants as ExecContainer (returns on session // start, with a goroutine running in the background to handle attach). // The HTTP attach itself maintains the same invariants as HTTPAttach. - ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (int, chan error, error) + // newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty + ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, r *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *define.TerminalSize) (int, chan error, error) // ExecContainerDetached executes a command in a running container, but // does not attach to it. Returns the PID of the exec session and an // error (if starting the exec session failed) diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index b5040de3e6..de435b58a8 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -94,17 +94,18 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- // this ensures attachToExec gets all of the output of the called process // conmon will then send the exit code of the exec process, or an error in the exec session // startFd must be the input side of the fd. +// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty // conmon will wait to start the exec session until the parent process has setup the console socket. // Once attachToExec successfully attaches to the console socket, the child conmon process responsible for calling runtime exec // will read from the output side of start fd, thus learning to start the child process. // Thus, the order goes as follow: // 1. conmon parent process sets up its console socket. sends on attachFd -// 2. attachToExec attaches to the console socket after reading on attachFd +// 2. attachToExec attaches to the console socket after reading on attachFd and resizes the tty // 3. child waits on startFd for attachToExec to attach to said console socket // 4. attachToExec sends on startFd, signalling it has attached to the socket and child is ready to go // 5. child receives on startFd, runs the runtime exec command // attachToExec is responsible for closing startFd and attachFd -func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File) error { +func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File, newSize *define.TerminalSize) error { if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } @@ -137,6 +138,14 @@ func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, se return err } + // resize before we start the container process + if newSize != nil { + err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize) + if err != nil { + logrus.Warn("resize failed", err) + } + } + // 2: then attach conn, err := openUnixSocket(sockPath) if err != nil { diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index 76338b86c0..09d3d18334 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -25,7 +25,7 @@ import ( ) // ExecContainer executes a command in a running container -func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) { +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *define.TerminalSize) (int, chan error, error) { if options == nil { return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") } @@ -68,7 +68,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options attachChan := make(chan error) go func() { // attachToExec is responsible for closing pipes - attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe) + attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe, newSize) close(attachChan) }() @@ -83,7 +83,8 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options // ExecContainerHTTP executes a new command in an existing container and // forwards its standard streams over an attach -func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (int, chan error, error) { +func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *define.TerminalSize) (int, chan error, error) { if streams != nil { if !streams.Stdin && !streams.Stdout && !streams.Stderr { return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") @@ -133,7 +134,7 @@ func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, o conmonPipeDataChan := make(chan conmonPipeData) go func() { // attachToExec is responsible for closing pipes - attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog) + attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog, newSize) close(attachChan) }() @@ -486,7 +487,7 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex } // Attach to a container over HTTP -func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, execCmd *exec.Cmd, conmonPipeDataChan chan<- conmonPipeData, ociLog string) (deferredErr error) { +func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, execCmd *exec.Cmd, conmonPipeDataChan chan<- conmonPipeData, ociLog string, newSize *define.TerminalSize) (deferredErr error) { // NOTE: As you may notice, the attach code is quite complex. // Many things happen concurrently and yet are interdependent. // If you ever change this function, make sure to write to the @@ -524,6 +525,14 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp return err } + // resize before we start the container process + if newSize != nil { + err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize) + if err != nil { + logrus.Warn("resize failed", err) + } + } + // 2: then attach conn, err := openUnixSocket(sockPath) if err != nil { diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 10526f368d..fcf2ffca84 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -119,12 +119,13 @@ func (r *MissingRuntime) AttachResize(ctr *Container, newSize define.TerminalSiz } // ExecContainer is not available as the runtime is missing -func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) { +func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *define.TerminalSize) (int, chan error, error) { return -1, nil, r.printError() } // ExecContainerHTTP is not available as the runtime is missing -func (r *MissingRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (int, chan error, error) { +func (r *MissingRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *define.TerminalSize) (int, chan error, error) { return -1, nil, r.printError() } diff --git a/libpod/options.go b/libpod/options.go index be26ced998..deb78a9e02 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -7,6 +7,7 @@ import ( "strings" "syscall" + "github.com/containers/buildah/pkg/parse" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" @@ -268,8 +269,11 @@ func WithRegistriesConf(path string) RuntimeOption { return errors.Wrap(err, "error locating specified registries.conf") } if rt.imageContext == nil { - rt.imageContext = &types.SystemContext{} + rt.imageContext = &types.SystemContext{ + BigFilesTemporaryDir: parse.GetTempDir(), + } } + rt.imageContext.SystemRegistriesConfPath = path return nil } @@ -1641,6 +1645,19 @@ func WithVolumeGID(gid int) VolumeCreateOption { } } +// WithVolumeNoChown prevents the volume from being chowned to the process uid at first use. +func WithVolumeNoChown() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.state.NeedsChown = false + + return nil + } +} + // withSetAnon sets a bool notifying libpod that this volume is anonymous and // should be removed when containers using it are removed and volumes are // specified for removal. diff --git a/libpod/runtime.go b/libpod/runtime.go index 2cf2b3f79b..7099f55f20 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -15,6 +15,7 @@ import ( "syscall" "time" + "github.com/containers/buildah/pkg/parse" "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/pkg/sysregistriesv2" @@ -379,7 +380,9 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // Set up containers/image if runtime.imageContext == nil { - runtime.imageContext = &types.SystemContext{} + runtime.imageContext = &types.SystemContext{ + BigFilesTemporaryDir: parse.GetTempDir(), + } } runtime.imageContext.SignaturePolicyPath = runtime.config.Engine.SignaturePolicyPath @@ -458,7 +461,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { } // Set up the CNI net plugin - netPlugin, err := ocicni.InitCNI(runtime.config.Network.DefaultNetwork, runtime.config.Network.NetworkConfigDir, runtime.config.Network.CNIPluginDirs...) + netPlugin, err := ocicni.InitCNINoInotify(runtime.config.Network.DefaultNetwork, runtime.config.Network.NetworkConfigDir, "", runtime.config.Network.CNIPluginDirs...) if err != nil { return errors.Wrapf(err, "error configuring CNI network plugin") } diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 694cdd1490..19008a2530 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -39,8 +39,23 @@ func (v *Volume) needsMount() bool { return true } - // Local driver with options needs mount - return len(v.config.Options) > 0 + // Commit 28138dafcc added the UID and GID options to this map + // However we should only mount when options other than uid and gid are set. + // see https://github.com/containers/podman/issues/10620 + index := 0 + if _, ok := v.config.Options["UID"]; ok { + index++ + } + if _, ok := v.config.Options["GID"]; ok { + index++ + } + // when uid or gid is set there is also the "o" option + // set so we have to ignore this one as well + if index > 0 { + index++ + } + // Local driver with options other than uid,gid needs mount + return len(v.config.Options) > index } // update() updates the volume state from the DB. diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 6bc02dd2b8..2a0a0b725c 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -403,6 +403,24 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, state.Status = define.ContainerStateCreated.String() } + state.Health = &types.Health{ + Status: inspect.State.Healthcheck.Status, + FailingStreak: inspect.State.Healthcheck.FailingStreak, + } + + log := inspect.State.Healthcheck.Log + + for _, item := range log { + res := &types.HealthcheckResult{} + s, _ := time.Parse(time.RFC3339Nano, item.Start) + e, _ := time.Parse(time.RFC3339Nano, item.End) + res.Start = s + res.End = e + res.ExitCode = item.ExitCode + res.Output = item.Output + state.Health.Log = append(state.Health.Log, res) + } + formatCapabilities(inspect.HostConfig.CapDrop) formatCapabilities(inspect.HostConfig.CapAdd) diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 1b7b884e05..77e62c1126 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -178,8 +178,16 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { logrus.Error(errors.Wrapf(e, "error attaching to container %s exec session %s", sessionCtr.ID(), sessionID)) } + var size *define.TerminalSize + if bodyParams.Tty && (bodyParams.Height > 0 || bodyParams.Width > 0) { + size = &define.TerminalSize{ + Height: bodyParams.Height, + Width: bodyParams.Width, + } + } + hijackChan := make(chan bool, 1) - err = sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan) + err = sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan, size) if <-hijackChan { // If connection was Hijacked, we have to signal it's being closed diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 9c4dd8638a..e933b98119 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -189,8 +189,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { var devices = []string{} if _, found := r.URL.Query()["devices"]; found { var m = []string{} - if err := json.Unmarshal([]byte(query.DropCapabilities), &m); err != nil { - utils.BadRequest(w, "devices", query.DropCapabilities, err) + if err := json.Unmarshal([]byte(query.Devices), &m); err != nil { + utils.BadRequest(w, "devices", query.Devices, err) return } devices = m diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go index f65e313fc7..844fb74c4c 100644 --- a/pkg/api/handlers/compat/resize.go +++ b/pkg/api/handlers/compat/resize.go @@ -73,7 +73,7 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { return } if err := ctnr.ExecResize(name, sz); err != nil { - if errors.Cause(err) != define.ErrCtrStateInvalid || !query.IgnoreNotRunning { + if errors.Cause(err) != define.ErrExecSessionStateInvalid || !query.IgnoreNotRunning { utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session")) return } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 52d7633af2..d350242b44 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -166,8 +166,10 @@ type ExecCreateResponse struct { } type ExecStartConfig struct { - Detach bool `json:"Detach"` - Tty bool `json:"Tty"` + Detach bool `json:"Detach"` + Tty bool `json:"Tty"` + Height uint16 `json:"h"` + Width uint16 `json:"w"` } func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index 3716ef6a2f..e353d714cb 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -269,10 +269,16 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // properties: // Detach: // type: boolean - // description: Detach from the command. Not presently supported. + // description: Detach from the command. // Tty: // type: boolean - // description: Allocate a pseudo-TTY. Presently ignored. + // description: Allocate a pseudo-TTY. + // h: + // type: integer + // description: Height of the TTY session in characters. Tty must be set to true to use it. + // w: + // type: integer + // description: Width of the TTY session in characters. Tty must be set to true to use it. // produces: // - application/json // responses: diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index adef1e7c84..cc12c8ab7f 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -343,7 +343,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w)) } if resizeErr != nil { - logrus.Warnf("failed to resize TTY: %v", resizeErr) + logrus.Infof("failed to resize TTY: %v", resizeErr) } } @@ -408,6 +408,17 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar // If we are in TTY mode, we need to set raw mode for the terminal. // TODO: Share all of this with Attach() for containers. needTTY := terminalFile != nil && terminal.IsTerminal(int(terminalFile.Fd())) && isTerm + + body := struct { + Detach bool `json:"Detach"` + TTY bool `json:"Tty"` + Height uint16 `json:"h"` + Width uint16 `json:"w"` + }{ + Detach: false, + TTY: needTTY, + } + if needTTY { state, err := setRawTerminal(terminalFile) if err != nil { @@ -419,13 +430,14 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar } logrus.SetFormatter(&logrus.TextFormatter{}) }() + w, h, err := terminal.GetSize(int(terminalFile.Fd())) + if err != nil { + logrus.Warnf("failed to obtain TTY size: %v", err) + } + body.Width = uint16(w) + body.Height = uint16(h) } - body := struct { - Detach bool `json:"Detach"` - }{ - Detach: false, - } bodyJSON, err := json.Marshal(body) if err != nil { return err diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index c7d432b16c..937d053308 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -299,6 +299,22 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO tarContent := []string{options.ContextDirectory} newContainerFiles := []string{} for _, c := range containerFiles { + if c == "/dev/stdin" { + content, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + tmpFile, err := ioutil.TempFile("", "build") + if err != nil { + return nil, err + } + defer os.Remove(tmpFile.Name()) // clean up + defer tmpFile.Close() + if _, err := tmpFile.Write(content); err != nil { + return nil, err + } + c = tmpFile.Name() + } containerfile, err := filepath.Abs(c) if err != nil { logrus.Errorf("cannot find absolute path of %v: %v", c, err) diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 911edeb5be..9cb32a3644 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -165,14 +165,13 @@ func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) if _, found := exclude[name]; found { continue } - isSymLink := false fileInfo, err := os.Stat(cgroupRoot + "/" + name) if err != nil { - isSymLink = !fileInfo.IsDir() + continue } c := controller{ name: name, - symlink: isSymLink, + symlink: !fileInfo.IsDir(), } controllers = append(controllers, c) } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 3cc46ed0a0..d4cafb6885 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -184,7 +184,7 @@ type ImagePushOptions struct { // image to the file. Ignored for remote calls. DigestFile string // Format is the Manifest type (oci, v2s1, or v2s2) to use when pushing an - // image using the 'dir' transport. Default is manifest type of source. + // image. Default is manifest type of source, with fallbacks. // Ignored for remote calls. Format string // Quiet can be specified to suppress pull progress when pulling. Ignored diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go index 1c590d2d60..56c747711a 100644 --- a/pkg/domain/infra/abi/parse/parse.go +++ b/pkg/domain/infra/abi/parse/parse.go @@ -37,7 +37,7 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1]) } logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) - libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID)) + libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID), libpod.WithVolumeNoChown()) finalVal = append(finalVal, o) // set option "UID": "$uid" volumeOptions["UID"] = splitO[1] @@ -50,7 +50,7 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1]) } logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) - libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID)) + libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID), libpod.WithVolumeNoChown()) finalVal = append(finalVal, o) // set option "GID": "$gid" volumeOptions["GID"] = splitO[1] diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index ab71f8f6fb..09c0f802d2 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -15,12 +15,13 @@ import ( // ExecAttachCtr execs and attaches to a container func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (int, error) { - resize := make(chan define.TerminalSize) + var resize chan define.TerminalSize haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize // events, and set the terminal to raw mode if haveTerminal && execConfig.Terminal { + resize = make(chan define.TerminalSize) cancel, oldTermState, err := handleTerminalAttach(ctx, resize) if err != nil { return -1, err @@ -32,7 +33,6 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo } }() } - return ctr.Exec(execConfig, streams, resize) } diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index 6255690b17..abaacdd5e9 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -604,4 +604,38 @@ RUN echo hello`, ALPINE) Expect(inspect.OutputToString()).To(Equal("windows")) }) + + It("podman build device test", func() { + if _, err := os.Lstat("/dev/fuse"); err != nil { + Skip(fmt.Sprintf("test requires stat /dev/fuse to work: %v", err)) + } + containerfile := fmt.Sprintf(`FROM %s +RUN ls /dev/fuse`, ALPINE) + containerfilePath := filepath.Join(podmanTest.TempDir, "Containerfile") + err := ioutil.WriteFile(containerfilePath, []byte(containerfile), 0755) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"build", "--pull-never", "-t", "test", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + session = podmanTest.Podman([]string{"build", "--pull-never", "--device", "/dev/fuse", "-t", "test", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman build device rename test", func() { + SkipIfRootless("rootless builds do not currently support renaming devices") + containerfile := fmt.Sprintf(`FROM %s +RUN ls /dev/test1`, ALPINE) + containerfilePath := filepath.Join(podmanTest.TempDir, "Containerfile") + err := ioutil.WriteFile(containerfilePath, []byte(containerfile), 0755) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"build", "--pull-never", "-t", "test", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + session = podmanTest.Podman([]string{"build", "--pull-never", "--device", "/dev/zero:/dev/test1", "-t", "test", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index f5b70d6bf4..83d185be12 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -101,11 +101,11 @@ var _ = Describe("Podman Info", func() { u, err := user.Current() Expect(err).To(BeNil()) + // Cannot use podmanTest.Podman() and test for storage path expect := filepath.Join("/tmp", os.Getenv("HOME"), u.Username, u.Uid, "storage") podmanPath := podmanTest.PodmanTest.PodmanBinary - cmd := exec.Command(podmanPath, "info", "--format", "{{.Store.GraphRoot}}") + cmd := exec.Command(podmanPath, "info", "--format", "{{.Store.GraphRoot -}}") out, err := cmd.CombinedOutput() - fmt.Println(string(out)) Expect(err).To(BeNil()) Expect(string(out)).To(Equal(expect)) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index f27ded5d26..174714cacf 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -904,6 +904,18 @@ USER bin`, BB) Expect(session.ExitCode()).To(Equal(100)) }) + It("podman run with named volume", func() { + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "stat", "-c", "%a %Y", "/var/tmp"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + perms := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--rm", "-v", "test:/var/tmp", ALPINE, "stat", "-c", "%a %Y", "/var/tmp"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(perms)) + }) + It("podman run with built-in volume image", func() { session := podmanTest.Podman([]string{"run", "--rm", redis, "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 9b77aaef84..4be1b20098 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -668,4 +668,36 @@ USER testuser`, fedoraMinimal) Expect(strings.Contains(test2.OutputToString(), testString)).To(BeTrue()) }) + + It("podman volume with uid and gid works", func() { + volName := "testVol" + volCreate := podmanTest.Podman([]string{"volume", "create", "--opt", "o=uid=1000", volName}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate.ExitCode()).To(Equal(0)) + + volMount := podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/test", volName), ALPINE, "stat", "-c", "%u", "/test"}) + volMount.WaitWithDefaultTimeout() + Expect(volMount.ExitCode()).To(Equal(0)) + Expect(volMount.OutputToString()).To(Equal("1000")) + + volName = "testVol2" + volCreate = podmanTest.Podman([]string{"volume", "create", "--opt", "o=gid=1000", volName}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate.ExitCode()).To(Equal(0)) + + volMount = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/test", volName), ALPINE, "stat", "-c", "%g", "/test"}) + volMount.WaitWithDefaultTimeout() + Expect(volMount.ExitCode()).To(Equal(0)) + Expect(volMount.OutputToString()).To(Equal("1000")) + + volName = "testVol3" + volCreate = podmanTest.Podman([]string{"volume", "create", "--opt", "o=uid=1000,gid=1000", volName}) + volCreate.WaitWithDefaultTimeout() + Expect(volCreate.ExitCode()).To(Equal(0)) + + volMount = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/test", volName), ALPINE, "stat", "-c", "%u:%g", "/test"}) + volMount.WaitWithDefaultTimeout() + Expect(volMount.ExitCode()).To(Equal(0)) + Expect(volMount.OutputToString()).To(Equal("1000:1000")) + }) }) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index bda331e6bf..16f1b04eff 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -19,21 +19,22 @@ load helpers @test "podman images - custom formats" { tests=" ---format {{.ID}} | [0-9a-f]\\\{12\\\} ---format {{.ID}} --no-trunc | sha256:[0-9a-f]\\\{64\\\} ---format {{.Repository}}:{{.Tag}} | $PODMAN_TEST_IMAGE_FQN ---format {{.Labels.created_by}} | test/system/build-testimage ---format {{.Labels.created_at}} | 20[0-9-]\\\+T[0-9:]\\\+Z +{{.ID}} | [0-9a-f]\\\{12\\\} +{{.ID| upper}} | [0-9A-F]\\\{12\\\} +{{.Repository}}:{{.Tag}} | $PODMAN_TEST_IMAGE_FQN +{{.Labels.created_by}} | test/system/build-testimage +{{.Labels.created_at}} | 20[0-9-]\\\+T[0-9:]\\\+Z " parse_table "$tests" | while read fmt expect; do - run_podman images $fmt + run_podman images --format "$fmt" is "$output" "$expect\$" "podman images $fmt" done + run_podman images --format "{{.ID}}" --no-trunc + is "$output" "sha256:[0-9a-f]\\{64\\}\$" "podman images --no-trunc" } - @test "podman images - json" { # 'created': podman includes fractional seconds, podman-remote does not tests=" diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 55392ea47c..f0d5413547 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -690,4 +690,18 @@ json-file | f run_podman rm $cid } +@test "podman run no /etc/mtab " { + tmpdir=$PODMAN_TMPDIR/build-test + mkdir -p $tmpdir + + cat >$tmpdir/Dockerfile <$containerfile < /$rand_filename +EOF + + # The 'apk' command can take a long time to fetch files; bump timeout + PODMAN_TIMEOUT=240 run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile + is "$output" ".*STEP 4: COMMIT" "COMMIT seen in log" + + run_podman run --rm build_test cat /$rand_filename + is "$output" "$rand_content" "reading generated file in image" + + run_podman rmi -f build_test +} + @test "podman build - global runtime flags test" { skip_if_remote "--runtime-flag flag not supported for remote" diff --git a/test/system/450-interactive.bats b/test/system/450-interactive.bats index a2db39492c..47bdff9abc 100644 --- a/test/system/450-interactive.bats +++ b/test/system/450-interactive.bats @@ -57,7 +57,18 @@ function teardown() { # ...and make sure stty under podman reads that. run_podman run -it --name mystty $IMAGE stty size <$PODMAN_TEST_PTY - is "$output" "$rows $cols" "stty under podman reads the correct dimensions" + is "$output" "$rows $cols" "stty under podman run reads the correct dimensions" + + run_podman rm -f mystty + + # FIXME: the checks below are flaking a lot (see #10710). + + # check that the same works for podman exec +# run_podman run -d --name mystty $IMAGE top +# run_podman exec -it mystty stty size <$PODMAN_TEST_PTY +# is "$output" "$rows $cols" "stty under podman exec reads the correct dimensions" +# +# run_podman rm -f mystty } diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 55ec80bb26..d55a786f79 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -162,23 +162,25 @@ load helpers done } -@test "podman run with slirp4ns assigns correct gateway address to host.containers.internal" { +@test "podman run with slirp4ns assigns correct addresses to /etc/hosts" { CIDR="$(random_rfc1918_subnet)" - run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ - $IMAGE grep 'host.containers.internal' /etc/hosts - is "$output" "${CIDR}.2 host.containers.internal" "host.containers.internal should be the cidr+2 address" + local conname=con-$(random_string 10) + run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \ + --name $conname --hostname $conname $IMAGE cat /etc/hosts + is "$output" ".*${CIDR}.2 host.containers.internal" "host.containers.internal should be the cidr+2 address" + is "$output" ".*${CIDR}.100 $conname $conname" "$conname should be the cidr+100 address" } @test "podman run with slirp4ns adds correct dns address to resolv.conf" { CIDR="$(random_rfc1918_subnet)" - run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \ $IMAGE grep "${CIDR}" /etc/resolv.conf is "$output" "nameserver ${CIDR}.3" "resolv.conf should have slirp4netns cidr+3 as a nameserver" } @test "podman run with slirp4ns assigns correct ip address container" { CIDR="$(random_rfc1918_subnet)" - run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \ $IMAGE sh -c "ip address | grep ${CIDR}" is "$output" ".*inet ${CIDR}.100/24 \+" "container should have slirp4netns cidr+100 assigned to interface" } diff --git a/vendor/github.com/containers/common/libimage/download.go b/vendor/github.com/containers/common/libimage/download.go index 5ea11f084f..54edf1b9a4 100644 --- a/vendor/github.com/containers/common/libimage/download.go +++ b/vendor/github.com/containers/common/libimage/download.go @@ -11,7 +11,7 @@ import ( ) // tmpdir returns a path to a temporary directory. -func (r *Runtime) tmpdir() string { +func tmpdir() string { tmpdir := os.Getenv("TMPDIR") if tmpdir == "" { tmpdir = "/var/tmp" @@ -25,7 +25,7 @@ func (r *Runtime) tmpdir() string { func (r *Runtime) downloadFromURL(source string) (string, error) { fmt.Printf("Downloading from %q\n", source) - outFile, err := ioutil.TempFile(r.tmpdir(), "import") + outFile, err := ioutil.TempFile(r.systemContext.BigFilesTemporaryDir, "import") if err != nil { return "", errors.Wrap(err, "error creating file") } diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go index 5e1b6a411e..71a4cce1da 100644 --- a/vendor/github.com/containers/common/libimage/runtime.go +++ b/vendor/github.com/containers/common/libimage/runtime.go @@ -84,6 +84,9 @@ func RuntimeFromStore(store storage.Store, options *RuntimeOptions) (*Runtime, e } else { systemContext = types.SystemContext{} } + if systemContext.BigFilesTemporaryDir == "" { + systemContext.BigFilesTemporaryDir = tmpdir() + } setRegistriesConfPath(&systemContext) diff --git a/vendor/github.com/containers/common/pkg/report/template.go b/vendor/github.com/containers/common/pkg/report/template.go index f7b4506bb3..f86b070344 100644 --- a/vendor/github.com/containers/common/pkg/report/template.go +++ b/vendor/github.com/containers/common/pkg/report/template.go @@ -130,7 +130,7 @@ func NewTemplate(name string) *Template { func (t *Template) Parse(text string) (*Template, error) { if strings.HasPrefix(text, "table ") { t.isTable = true - text = "{{range .}}" + NormalizeFormat(text) + "{{end}}" + text = "{{range .}}" + NormalizeFormat(text) + "{{end -}}" } else { text = NormalizeFormat(text) } @@ -157,12 +157,12 @@ func (t *Template) IsTable() bool { return t.isTable } -var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*}}`) +var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*-?\s*}}`) // EnforceRange ensures that the format string contains a range func EnforceRange(format string) string { if !rangeRegex.MatchString(format) { - return "{{range .}}" + format + "{{end}}" + return "{{range .}}" + format + "{{end -}}" } return format } diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 60cfd6acdb..185c6e43ce 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.38.10" +const Version = "0.38.11" diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go index b383401267..90d5b6c508 100644 --- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go +++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go @@ -195,16 +195,21 @@ func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) { // If defaultNetName is empty, CNI config files should be reloaded real-time and // defaultNetName should be changeable and determined by file sorting. func InitCNI(defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) { - return initCNI(nil, "", defaultNetName, confDir, binDirs...) + return initCNI(nil, "", defaultNetName, confDir, true, binDirs...) } // InitCNIWithCache works like InitCNI except that it takes the cni cache directory as third param. func InitCNIWithCache(defaultNetName, confDir, cacheDir string, binDirs ...string) (CNIPlugin, error) { - return initCNI(nil, cacheDir, defaultNetName, confDir, binDirs...) + return initCNI(nil, cacheDir, defaultNetName, confDir, true, binDirs...) +} + +// InitCNINoInotify works like InitCNI except that it does not use inotify to watch for changes in the CNI config dir. +func InitCNINoInotify(defaultNetName, confDir, cacheDir string, binDirs ...string) (CNIPlugin, error) { + return initCNI(nil, cacheDir, defaultNetName, confDir, false, binDirs...) } // Internal function to allow faking out exec functions for testing -func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir string, binDirs ...string) (CNIPlugin, error) { +func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir string, useInotify bool, binDirs ...string) (CNIPlugin, error) { if confDir == "" { confDir = DefaultConfDir } @@ -245,22 +250,26 @@ func initCNI(exec cniinvoke.Exec, cacheDir, defaultNetName string, confDir strin plugin.syncNetworkConfig() - plugin.watcher, err = newWatcher(plugin.confDir) - if err != nil { - return nil, err - } + if useInotify { + plugin.watcher, err = newWatcher(plugin.confDir) + if err != nil { + return nil, err + } - startWg := sync.WaitGroup{} - startWg.Add(1) - go plugin.monitorConfDir(&startWg) - startWg.Wait() + startWg := sync.WaitGroup{} + startWg.Add(1) + go plugin.monitorConfDir(&startWg) + startWg.Wait() + } return plugin, nil } func (plugin *cniNetworkPlugin) Shutdown() error { close(plugin.shutdownChan) - plugin.watcher.Close() + if plugin.watcher != nil { + plugin.watcher.Close() + } plugin.done.Wait() return nil } @@ -539,10 +548,11 @@ func (plugin *cniNetworkPlugin) SetUpPodWithContext(ctx context.Context, podNetw results := make([]NetResult, 0) if err := plugin.forEachNetwork(&podNetwork, false, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + fullPodName := buildFullPodName(*podNetwork) + logrus.Infof("Adding pod %s to CNI network %q (type=%v)", fullPodName, network.name, network.config.Plugins[0].Network.Type) result, err := network.addToNetwork(ctx, rt, plugin.cniConfig) if err != nil { - logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err) - return err + return fmt.Errorf("error adding pod %s to CNI network %q: %v", fullPodName, network.name, err) } results = append(results, NetResult{ Result: result, @@ -654,8 +664,10 @@ func (plugin *cniNetworkPlugin) TearDownPodWithContext(ctx context.Context, podN } return plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + fullPodName := buildFullPodName(*podNetwork) + logrus.Infof("Deleting pod %s from CNI network %q (type=%v)", fullPodName, network.name, network.config.Plugins[0].Network.Type) if err := network.deleteFromNetwork(ctx, rt, plugin.cniConfig); err != nil { - return fmt.Errorf("Error while removing pod from CNI network %q: %s", network.name, err) + return fmt.Errorf("error removing pod %s from CNI network %q: %v", fullPodName, network.name, err) } return nil }) @@ -680,10 +692,11 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatusWithContext(ctx context.Conte results := make([]NetResult, 0) if err := plugin.forEachNetwork(&podNetwork, true, func(network *cniNetwork, podNetwork *PodNetwork, rt *libcni.RuntimeConf) error { + fullPodName := buildFullPodName(*podNetwork) + logrus.Infof("Checking pod %s for CNI network %s (type=%v)", fullPodName, network.name, network.config.Plugins[0].Network.Type) result, err := network.checkNetwork(ctx, rt, plugin.cniConfig, plugin.nsManager, podNetwork.NetNS) if err != nil { - logrus.Errorf("Error while checking pod to CNI network %q: %s", network.name, err) - return err + return fmt.Errorf("error checking pod %s for CNI network %q: %v", fullPodName, network.name, err) } if result != nil { results = append(results, NetResult{ @@ -703,19 +716,10 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatusWithContext(ctx context.Conte } func (network *cniNetwork) addToNetwork(ctx context.Context, rt *libcni.RuntimeConf, cni *libcni.CNIConfig) (cnitypes.Result, error) { - logrus.Infof("About to add CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) - res, err := cni.AddNetworkList(ctx, network.config, rt) - if err != nil { - logrus.Errorf("Error adding network: %v", err) - return nil, err - } - - return res, nil + return cni.AddNetworkList(ctx, network.config, rt) } func (network *cniNetwork) checkNetwork(ctx context.Context, rt *libcni.RuntimeConf, cni *libcni.CNIConfig, nsManager *nsManager, netns string) (cnitypes.Result, error) { - logrus.Infof("About to check CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) - gtet, err := cniversion.GreaterThanOrEqualTo(network.config.CNIVersion, "0.4.0") if err != nil { return nil, err @@ -786,11 +790,7 @@ func (network *cniNetwork) checkNetwork(ctx context.Context, rt *libcni.RuntimeC } func (network *cniNetwork) deleteFromNetwork(ctx context.Context, rt *libcni.RuntimeConf, cni *libcni.CNIConfig) error { - logrus.Infof("About to del CNI network %s (type=%v)", network.name, network.config.Plugins[0].Network.Type) - if err := cni.DelNetworkList(ctx, network.config, rt); err != nil { - return err - } - return nil + return cni.DelNetworkList(ctx, network.config, rt) } func buildCNIRuntimeConf(podNetwork *PodNetwork, ifName string, runtimeConfig RuntimeConfig) (*libcni.RuntimeConf, error) { @@ -809,6 +809,13 @@ func buildCNIRuntimeConf(podNetwork *PodNetwork, ifName string, runtimeConfig Ru CapabilityArgs: map[string]interface{}{}, } + // Propagate existing CNI_ARGS to non-k8s consumers + for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") { + if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 { + rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]}) + } + } + // Add requested static IP to CNI_ARGS ip := runtimeConfig.IP if ip != "" { diff --git a/vendor/modules.txt b/vendor/modules.txt index 6be35cb065..254aa23f18 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -91,7 +91,7 @@ github.com/containers/buildah/pkg/overlay github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/rusage github.com/containers/buildah/util -# github.com/containers/common v0.38.10 +# github.com/containers/common v0.38.11 github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/pkg/apparmor @@ -250,7 +250,7 @@ github.com/coreos/stream-metadata-go/fedoracoreos github.com/coreos/stream-metadata-go/fedoracoreos/internals github.com/coreos/stream-metadata-go/stream github.com/coreos/stream-metadata-go/stream/rhcos -# github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf +# github.com/cri-o/ocicni v0.2.1-0.20210621164014-d0acc7862283 github.com/cri-o/ocicni/pkg/ocicni # github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin