diff options
37 files changed, 608 insertions, 165 deletions
diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index 059f9050f..09ee5aa0c 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -10,7 +10,7 @@ import ( func (c *ContainerCLIOpts) validate() error { var () if c.Rm && c.Restart != "" && c.Restart != "no" { - return errors.Errorf("the --rm option conflicts with --restart") + return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) } if _, err := util.ValidatePullType(c.Pull); err != nil { diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index e7b88eb3f..84ae70b6a 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -233,7 +233,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // validate flags as needed if err := c.validate(); err != nil { - return nil + return err } s.User = c.User diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index 227c13f4c..a3ca6edf9 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -1,6 +1,7 @@ package containers import ( + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/cmd/podman/validate" @@ -52,11 +53,11 @@ func diff(cmd *cobra.Command, args []string) error { return err } - switch diffOpts.Format { - case "": - return report.ChangesToTable(results) - case "json": + switch { + case parse.MatchesJSONFormat(diffOpts.Format): return report.ChangesToJSON(results) + case diffOpts.Format == "": + return report.ChangesToTable(results) default: return errors.New("only supported value for '--format' is 'json'") } diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index a78b35c08..c4c8b60f3 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -240,7 +240,7 @@ func ps(cmd *cobra.Command, args []string) error { func createPsOut() (string, string) { var row string if listOpts.Namespace { - headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDN\tUSERNS\tUTS\n" + headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDNS\tUSERNS\tUTS\n" row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" return headers, row } diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index ffb341fc4..239da9d28 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -11,7 +11,9 @@ import ( "unicode" "github.com/containers/image/v5/docker/reference" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/report" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" "github.com/pkg/errors" @@ -106,9 +108,12 @@ func images(cmd *cobra.Command, args []string) error { switch { case listFlag.quiet: return writeID(imgs) - case cmd.Flag("format").Changed && listFlag.format == "json": + case parse.MatchesJSONFormat(listFlag.format): return writeJSON(imgs) default: + if cmd.Flag("format").Changed { + listFlag.noHeading = true // V1 compatibility + } return writeTemplate(imgs) } } @@ -156,25 +161,29 @@ func writeJSON(images []imageReporter) error { } func writeTemplate(imgs []imageReporter) error { - var ( - hdr, row string - ) - if len(listFlag.format) < 1 { - hdr, row = imageListFormat(listFlag) + hdrs := report.Headers(imageReporter{}, map[string]string{ + "ID": "IMAGE ID", + "ReadOnly": "R/O", + }) + + var row string + if listFlag.format == "" { + row = lsFormatFromFlags(listFlag) } else { - row = listFlag.format - if !strings.HasSuffix(row, "\n") { - row += "\n" - } + row = report.NormalizeFormat(listFlag.format) } - format := hdr + "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("list").Parse(format) - if err != nil { - return err - } - tmpl = template.Must(tmpl, nil) + + format := "{{range . }}" + row + "{{end}}" + tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() + + if !listFlag.noHeading { + if err := tmpl.Execute(w, hdrs); err != nil { + return err + } + } + return tmpl.Execute(w, imgs) } @@ -276,40 +285,27 @@ func sortFunc(key string, data []imageReporter) func(i, j int) bool { } } -func imageListFormat(flags listFlagType) (string, string) { - // Defaults - hdr := "REPOSITORY\tTAG" - row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}" +func lsFormatFromFlags(flags listFlagType) string { + row := []string{ + "{{if .Repository}}{{.Repository}}{{else}}<none>{{end}}", + "{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}", + } if flags.digests { - hdr += "\tDIGEST" - row += "\t{{.Digest}}" + row = append(row, "{{.Digest}}") } - hdr += "\tIMAGE ID" - row += "\t{{.ID}}" - - hdr += "\tCREATED\tSIZE" - row += "\t{{.Created}}\t{{.Size}}" + row = append(row, "{{.ID}}", "{{.Created}}", "{{.Size}}") if flags.history { - hdr += "\tHISTORY" - row += "\t{{if .History}}{{.History}}{{else}}<none>{{end}}" + row = append(row, "{{if .History}}{{.History}}{{else}}<none>{{end}}") } if flags.readOnly { - hdr += "\tReadOnly" - row += "\t{{.ReadOnly}}" - } - - if flags.noHeading { - hdr = "" - } else { - hdr += "\n" + row = append(row, "{{.ReadOnly}}") } - row += "\n" - return hdr, row + return strings.Join(row, "\t") + "\n" } type imageReporter struct { diff --git a/cmd/podman/parse/json.go b/cmd/podman/parse/json.go index 95a6633b8..40ac415db 100644 --- a/cmd/podman/parse/json.go +++ b/cmd/podman/parse/json.go @@ -2,8 +2,9 @@ package parse import "regexp" -var jsonFormatRegex = regexp.MustCompile(`^(\s*json\s*|\s*{{\s*json\s*\.\s*}}\s*)$`) +var jsonFormatRegex = regexp.MustCompile(`^\s*(json|{{\s*json\s*( \.)?\s*}})\s*$`) +// MatchesJSONFormat test CLI --format string to be a JSON request func MatchesJSONFormat(s string) bool { return jsonFormatRegex.Match([]byte(s)) } diff --git a/cmd/podman/parse/json_test.go b/cmd/podman/parse/json_test.go index 5cad185fd..ec3b5664b 100644 --- a/cmd/podman/parse/json_test.go +++ b/cmd/podman/parse/json_test.go @@ -1,6 +1,8 @@ package parse import ( + "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -13,18 +15,31 @@ func TestMatchesJSONFormat(t *testing.T) { }{ {"json", true}, {" json", true}, - {"json ", true}, + {" json ", true}, {" json ", true}, + {"{{json}}", true}, + {"{{json }}", true}, {"{{json .}}", true}, {"{{ json .}}", true}, - {"{{json . }}", true}, - {" {{ json . }} ", true}, - {"{{json }}", false}, - {"{{json .", false}, + {"{{ json . }}", true}, + {" {{ json . }} ", true}, + {"{{ json .", false}, {"json . }}", false}, + {"{{.ID }} json .", false}, + {"json .", false}, + {"{{json.}}", false}, } for _, tt := range tests { assert.Equal(t, tt.expected, MatchesJSONFormat(tt.input)) } + + for _, tc := range tests { + tc := tc + label := "MatchesJSONFormat/" + strings.ReplaceAll(tc.input, " ", "_") + t.Run(label, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, MatchesJSONFormat(tc.input), fmt.Sprintf("Scanning %q failed", tc.input)) + }) + } } diff --git a/cmd/podman/report/format.go b/cmd/podman/report/format.go new file mode 100644 index 000000000..32d92bec5 --- /dev/null +++ b/cmd/podman/report/format.go @@ -0,0 +1,68 @@ +package report + +import ( + "reflect" + "strings" +) + +// tableReplacer will remove 'table ' prefix and clean up tabs +var tableReplacer = strings.NewReplacer( + "table ", "", + `\t`, "\t", + `\n`, "\n", + " ", "\t", +) + +// escapedReplacer will clean up escaped characters from CLI +var escapedReplacer = strings.NewReplacer( + `\t`, "\t", + `\n`, "\n", +) + +// NormalizeFormat reads given go template format provided by CLI and munges it into what we need +func NormalizeFormat(format string) string { + f := format + // two replacers used so we only remove the prefix keyword `table` + if strings.HasPrefix(f, "table ") { + f = tableReplacer.Replace(f) + } else { + f = escapedReplacer.Replace(format) + } + + if !strings.HasSuffix(f, "\n") { + f += "\n" + } + + return f +} + +// Headers queries the interface for field names +func Headers(object interface{}, overrides map[string]string) []map[string]string { + value := reflect.ValueOf(object) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + // Column header will be field name upper-cased. + headers := make(map[string]string, value.NumField()) + for i := 0; i < value.Type().NumField(); i++ { + field := value.Type().Field(i) + // Recurse to find field names from promoted structs + if field.Type.Kind() == reflect.Struct && field.Anonymous { + h := Headers(reflect.New(field.Type).Interface(), nil) + for k, v := range h[0] { + headers[k] = v + } + continue + } + headers[field.Name] = strings.ToUpper(field.Name) + } + + if len(overrides) > 0 { + // Override column header as provided + for k, v := range overrides { + headers[k] = strings.ToUpper(v) + } + } + return []map[string]string{headers} +} diff --git a/cmd/podman/report/format_test.go b/cmd/podman/report/format_test.go new file mode 100644 index 000000000..7dd62e899 --- /dev/null +++ b/cmd/podman/report/format_test.go @@ -0,0 +1,35 @@ +package report + +import ( + "strings" + "testing" +) + +func TestNormalizeFormat(t *testing.T) { + cases := []struct { + format string + expected string + }{ + {"table {{.ID}}", "{{.ID}}\n"}, + {"table {{.ID}} {{.C}}", "{{.ID}}\t{{.C}}\n"}, + {"{{.ID}}", "{{.ID}}\n"}, + {"{{.ID}}\n", "{{.ID}}\n"}, + {"{{.ID}} {{.C}}", "{{.ID}} {{.C}}\n"}, + {"\t{{.ID}}", "\t{{.ID}}\n"}, + {`\t` + "{{.ID}}", "\t{{.ID}}\n"}, + {"table {{.ID}}\t{{.C}}", "{{.ID}}\t{{.C}}\n"}, + {"{{.ID}} table {{.C}}", "{{.ID}} table {{.C}}\n"}, + } + for _, tc := range cases { + tc := tc + + label := strings.ReplaceAll(tc.format, " ", "<sp>") + t.Run("NormalizeFormat/"+label, func(t *testing.T) { + t.Parallel() + actual := NormalizeFormat(tc.format) + if actual != tc.expected { + t.Errorf("Expected %q, actual %q", tc.expected, actual) + } + }) + } +} diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 6424ec12e..1e73f7540 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -154,34 +154,35 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { } } - if cmd.Flag("cpu-profile").Changed { - f, err := os.Create(cfg.CPUProfile) - if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - cfg.CPUProfile) - } - if err := pprof.StartCPUProfile(f); err != nil { - return err + if !registry.IsRemote() { + if cmd.Flag("cpu-profile").Changed { + f, err := os.Create(cfg.CPUProfile) + if err != nil { + return errors.Wrapf(err, "unable to create cpu profiling file %s", + cfg.CPUProfile) + } + if err := pprof.StartCPUProfile(f); err != nil { + return err + } } - } - if cmd.Flag("trace").Changed { - tracer, closer := tracing.Init("podman") - opentracing.SetGlobalTracer(tracer) - cfg.SpanCloser = closer + if cmd.Flag("trace").Changed { + tracer, closer := tracing.Init("podman") + opentracing.SetGlobalTracer(tracer) + cfg.SpanCloser = closer - cfg.Span = tracer.StartSpan("before-context") - cfg.SpanCtx = opentracing.ContextWithSpan(registry.Context(), cfg.Span) - opentracing.StartSpanFromContext(cfg.SpanCtx, cmd.Name()) - } + cfg.Span = tracer.StartSpan("before-context") + cfg.SpanCtx = opentracing.ContextWithSpan(registry.Context(), cfg.Span) + opentracing.StartSpanFromContext(cfg.SpanCtx, cmd.Name()) + } - if cfg.MaxWorks <= 0 { - return errors.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks) - } - if err := parallel.SetMaxThreads(uint(cfg.MaxWorks)); err != nil { - return err + if cfg.MaxWorks <= 0 { + return errors.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks) + } + if err := parallel.SetMaxThreads(uint(cfg.MaxWorks)); err != nil { + return err + } } - // Setup Rootless environment, IFF: // 1) in ABI mode // 2) running as non-root @@ -206,12 +207,14 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error { } cfg := registry.PodmanConfig() - if cmd.Flag("cpu-profile").Changed { - pprof.StopCPUProfile() - } - if cmd.Flag("trace").Changed { - cfg.Span.Finish() - cfg.SpanCloser.Close() + if !registry.IsRemote() { + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + if cmd.Flag("trace").Changed { + cfg.Span.Finish() + cfg.SpanCloser.Close() + } } registry.ImageEngine().Shutdown(registry.Context()) @@ -249,51 +252,57 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { srv, uri, ident := resolveDestination() lFlags := cmd.Flags() - lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") lFlags.StringVarP(&opts.Engine.ActiveService, "connection", "c", srv, "Connection to use for remote Podman service") lFlags.StringVar(&opts.URI, "url", uri, "URL to access Podman service (CONTAINER_HOST)") lFlags.StringVar(&opts.Identity, "identity", ident, "path to SSH identity file, (CONTAINER_SSHKEY)") + lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") pFlags := cmd.PersistentFlags() - pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") - pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") - pFlags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") - pFlags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") - pFlags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") - pFlags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") - pFlags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) - pFlags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") - pFlags.IntVar(&opts.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") - pFlags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") - pFlags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") - pFlags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") - pFlags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") - pFlags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") - // -s is deprecated due to conflict with -s on subcommands - pFlags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") - pFlags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") - - pFlags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") - pFlags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") - + if registry.IsRemote() { + if err := lFlags.MarkHidden("remote"); err != nil { + logrus.Warnf("unable to mark --remote flag as hidden: %s", err.Error()) + } + opts.Remote = true + } else { + pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") + pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") + pFlags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") + pFlags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") + pFlags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + pFlags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") + pFlags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) + pFlags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + pFlags.IntVar(&opts.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") + pFlags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") + pFlags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") + pFlags.StringVar(&opts.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") + pFlags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + pFlags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + // -s is deprecated due to conflict with -s on subcommands + pFlags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + pFlags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + + pFlags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") + pFlags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") + + // Hide these flags for both ABI and Tunneling + for _, f := range []string{ + "cpu-profile", + "default-mounts-file", + "max-workers", + "registries-conf", + "trace", + } { + if err := pFlags.MarkHidden(f); err != nil { + logrus.Warnf("unable to mark %s flag as hidden: %s", f, err.Error()) + } + } + } // Override default --help information of `--help` global flag var dummyHelp bool pFlags.BoolVar(&dummyHelp, "help", false, "Help for podman") pFlags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(logLevels, ", "))) - // Hide these flags for both ABI and Tunneling - for _, f := range []string{ - "cpu-profile", - "default-mounts-file", - "max-workers", - "registries-conf", - "trace", - } { - if err := pFlags.MarkHidden(f); err != nil { - logrus.Warnf("unable to mark %s flag as hidden: %s", f, err.Error()) - } - } - // Only create these flags for ABI connections if !registry.IsRemote() { pFlags.StringArrayVar(&opts.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") diff --git a/completions/bash/podman b/completions/bash/podman index a83cfc790..e12862126 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -407,7 +407,7 @@ __podman_local_interfaces() { __podman_complete_restart() { case "$prev" in --restart) - COMPREPLY=( $( compgen -W "always no on-failure" -- "$cur") ) + COMPREPLY=( $( compgen -W "always no on-failure unless-stopped" -- "$cur") ) return ;; esac diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 555486562..87337fa3c 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -160,6 +160,14 @@ Print the version Podman can set up environment variables from env of [engine] table in containers.conf. These variables can be overridden by passing environment variables before the `podman` commands. +## Remote Access + +The Podman command can be used with remote services using the `--remote` flag. Connections can +be made using local unix domain sockets, ssh or directly to tcp sockets. When specifying the +podman --remote flag, only the global options `--url`, `--identity`, `--log-level`, `--connection` are used. + +Connection information can also be managed using the containers.conf file. + ## Exit Status The exit code from `podman` gives information about why the container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index eba732d2a..514cdaee1 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "math" "net" "os" "os/user" @@ -35,6 +36,7 @@ import ( "github.com/containers/podman/v2/pkg/util" "github.com/containers/podman/v2/utils" "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/idtools" securejoin "github.com/cyphar/filepath-securejoin" runcuser "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -416,9 +418,43 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // Look up and add groups the user belongs to, if a group wasn't directly specified if !strings.Contains(c.config.User, ":") { + // the gidMappings that are present inside the container user namespace + var gidMappings []idtools.IDMap + + switch { + case len(c.config.IDMappings.GIDMap) > 0: + gidMappings = c.config.IDMappings.GIDMap + case rootless.IsRootless(): + // Check whether the current user namespace has enough gids available. + availableGids, err := rootless.GetAvailableGids() + if err != nil { + return nil, errors.Wrapf(err, "cannot read number of available GIDs") + } + gidMappings = []idtools.IDMap{{ + ContainerID: 0, + HostID: 0, + Size: int(availableGids), + }} + default: + gidMappings = []idtools.IDMap{{ + ContainerID: 0, + HostID: 0, + Size: math.MaxInt32, + }} + } for _, gid := range execUser.Sgids { - // FIXME: We need to add a flag to containers.conf to not add these for HPC Users. - g.AddProcessAdditionalGid(uint32(gid)) + isGidAvailable := false + for _, m := range gidMappings { + if gid >= m.ContainerID && gid < m.ContainerID+m.Size { + isGidAvailable = true + break + } + } + if isGidAvailable { + g.AddProcessAdditionalGid(uint32(gid)) + } else { + logrus.Warnf("additional gid=%d is not present in the user namespace, skip setting it", gid) + } } } diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 73c2df76e..d895171cf 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -33,7 +33,7 @@ const ( func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { var config journal.JournalReaderConfig if options.Tail < 0 { - config.NumFromTail = math.MaxUint64 + config.NumFromTail = 0 } else { config.NumFromTail = uint64(options.Tail) } diff --git a/libpod/kube.go b/libpod/kube.go index f83e99d82..6df79e394 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -303,12 +303,24 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) { // This should not be applicable //container.EnvFromSource = kubeContainer.Env = envVariables - // TODO enable resources when we can support naming conventions - //container.Resources kubeContainer.SecurityContext = kubeSec kubeContainer.StdinOnce = false kubeContainer.TTY = c.config.Spec.Process.Terminal + // TODO add CPU limit support. + if c.config.Spec.Linux != nil && + c.config.Spec.Linux.Resources != nil && + c.config.Spec.Linux.Resources.Memory != nil && + c.config.Spec.Linux.Resources.Memory.Limit != nil { + if kubeContainer.Resources.Limits == nil { + kubeContainer.Resources.Limits = v1.ResourceList{} + } + + qty := kubeContainer.Resources.Limits.Memory() + qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit) + kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty + } + return kubeContainer, kubeVolumes, nil } diff --git a/libpod/pod.go b/libpod/pod.go index a5a0532be..c8f62ca18 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -327,3 +327,21 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerSta } return newContainerStats, nil } + +// ProcessLabel returns the SELinux label associated with the pod +func (p *Pod) ProcessLabel() (string, error) { + if !p.HasInfraContainer() { + return "", nil + } + + id, err := p.InfraContainerID() + if err != nil { + return "", err + } + + ctr, err := p.runtime.state.Container(id) + if err != nil { + return "", err + } + return ctr.ProcessLabel(), nil +} diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 940b57343..cc67ebcd1 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -20,9 +20,25 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/docker/api/types" "github.com/gorilla/schema" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) +// mergeNameAndTagOrDigest creates an image reference as string from the +// provided image name and tagOrDigest which can be a tag, a digest or empty. +func mergeNameAndTagOrDigest(name, tagOrDigest string) string { + if len(tagOrDigest) == 0 { + return name + } + + separator := ":" // default to tag + if _, err := digest.Parse(tagOrDigest); err == nil { + // We have a digest, so let's change the separator. + separator = "@" + } + return fmt.Sprintf("%s%s%s", name, separator, tagOrDigest) +} + func ExportImage(w http.ResponseWriter, r *http.Request) { // 200 ok // 500 server @@ -252,10 +268,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { return } - fromImage := query.FromImage - if len(query.Tag) >= 1 { - fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) - } + fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag) authConf, authfile, key, err := auth.GetCredentials(r) if err != nil { diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 46e4df1d2..708ad06cb 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -25,7 +25,7 @@ var ( // the most recent number of containers. The pod and size booleans indicate that pod information and rootfs // size information should also be included. Finally, the sync bool synchronizes the OCI runtime and // container state. -func List(ctx context.Context, filters map[string][]string, all *bool, last *int, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck +func List(ctx context.Context, filters map[string][]string, all *bool, last *int, namespace, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -44,6 +44,9 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int if sync != nil { params.Set("sync", strconv.FormatBool(*sync)) } + if namespace != nil { + params.Set("namespace", strconv.FormatBool(*namespace)) + } if filters != nil { filterString, err := bindings.FiltersToString(filters) if err != nil { diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index bf2ceab2a..408b4769d 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -499,7 +499,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil) + containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil) Expect(err).To(BeNil()) err = containers.Kill(bt.conn, containerLatestList[0].Names[0], "SIGTERM") Expect(err).To(BeNil()) @@ -744,7 +744,7 @@ var _ = Describe("Podman containers ", func() { // Validate list container with id filter filters := make(map[string][]string) filters["id"] = []string{cid} - c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil) + c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil, nil) Expect(err).To(BeNil()) Expect(len(c)).To(Equal(1)) }) @@ -758,7 +758,7 @@ var _ = Describe("Podman containers ", func() { lastNum := 1 - c, err := containers.List(bt.conn, nil, bindings.PTrue, &lastNum, nil, nil) + c, err := containers.List(bt.conn, nil, bindings.PTrue, &lastNum, nil, nil, nil) Expect(err).To(BeNil()) Expect(len(c)).To(Equal(1)) Expect(c[0].PodName).To(Equal(podName)) diff --git a/pkg/channel/writer.go b/pkg/channel/writer.go index dbb38e416..28c9d7de5 100644 --- a/pkg/channel/writer.go +++ b/pkg/channel/writer.go @@ -1,6 +1,7 @@ package channel import ( + "fmt" "io" "sync" @@ -32,16 +33,24 @@ func (w *writeCloser) Chan() <-chan []byte { } // Write method for WriteCloser -func (w *writeCloser) Write(b []byte) (int, error) { +func (w *writeCloser) Write(b []byte) (bLen int, err error) { + // https://github.com/containers/podman/issues/7896 + // when podman-remote pull image, if it was killed, the server will panic: send on closed channel + // so handle it + defer func() { + if rErr := recover(); rErr != nil { + err = fmt.Errorf("%s", rErr) + } + }() if w == nil || w.ch == nil { return 0, errors.New("use channel.NewWriter() to initialize a WriteCloser") } w.mux.Lock() + defer w.mux.Unlock() buf := make([]byte, len(b)) copy(buf, b) w.ch <- buf - w.mux.Unlock() return len(b), nil } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 9b03503c6..194bb4b48 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -548,7 +548,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { - return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Size, &options.Sync) + return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Namespace, &options.Size, &options.Sync) } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 7d28f1bd3..63f9546be 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -19,7 +19,7 @@ func getContainersByContext(contextWithConnection context.Context, all, ignore b return nil, errors.New("cannot lookup containers and all") } - allContainers, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, bindings.PTrue) + allContainers, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, nil, bindings.PTrue) if err != nil { return nil, err } diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 8087507e2..96b2d754f 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -134,15 +134,16 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) } + pid, err = c.PID() + if err != nil { + return errors.Wrapf(err, "unable to obtain container pid") + } + if !opts.Size && !opts.Namespace { return nil } if opts.Namespace { - pid, err = c.PID() - if err != nil { - return errors.Wrapf(err, "unable to obtain container pid") - } ctrPID := strconv.Itoa(pid) cgroup, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) ipc, _ = getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go index d02721ea9..799c793d8 100644 --- a/pkg/rootless/rootless.go +++ b/pkg/rootless/rootless.go @@ -2,8 +2,10 @@ package rootless import ( "os" + "sync" "github.com/containers/storage" + "github.com/opencontainers/runc/libcontainer/user" "github.com/pkg/errors" ) @@ -46,3 +48,26 @@ func TryJoinPauseProcess(pausePidPath string) (bool, int, error) { } return became, ret, err } + +var ( + availableGids int64 + availableGidsErr error + availableGidsOnce sync.Once +) + +// GetAvailableGids returns how many GIDs are available in the +// current user namespace. +func GetAvailableGids() (int64, error) { + availableGidsOnce.Do(func() { + idMap, err := user.ParseIDMapFile("/proc/self/gid_map") + if err != nil { + availableGidsErr = err + return + } + availableGids = int64(0) + for _, r := range idMap { + availableGids += r.Count + } + }) + return availableGids, availableGidsErr +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 2ac3b376f..147450703 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v2/pkg/specgen" "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" + "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -272,6 +273,21 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. // Security options if len(s.SelinuxOpts) > 0 { options = append(options, libpod.WithSecLabels(s.SelinuxOpts)) + } else { + if pod != nil { + // duplicate the security options from the pod + processLabel, err := pod.ProcessLabel() + if err != nil { + return nil, err + } + if processLabel != "" { + selinuxOpts, err := label.DupSecOpt(processLabel) + if err != nil { + return nil, err + } + options = append(options, libpod.WithSecLabels(selinuxOpts)) + } + } } options = append(options, libpod.WithPrivileged(s.Privileged)) diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index b57ddf1aa..f02432f5b 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/specgen" - "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" @@ -200,7 +199,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } gid5Available := true if isRootless { - nGids, err := GetAvailableGids() + nGids, err := rootless.GetAvailableGids() if err != nil { return nil, err } @@ -360,15 +359,3 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt return configSpec, nil } - -func GetAvailableGids() (int64, error) { - idMap, err := user.ParseIDMapFile("/proc/self/gid_map") - if err != nil { - return 0, err - } - count := int64(0) - for _, r := range idMap { - count += r.Count - } - return count, nil -} diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index a204df65c..bdc298ae3 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -39,7 +39,11 @@ t GET images/$iid/json 200 \ .Id=sha256:$iid \ .RepoTags[0]=$IMAGE -#t POST images/create fromImage=alpine 201 foo +t POST "images/create?fromImage=alpine" '' 200 + +t POST "images/create?fromImage=alpine&tag=latest" '' 200 + +t POST "images/create?fromImage=docker.io/library/alpine&tag=sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f" '' 200 # Display the image history t GET libpod/images/nonesuch/history 404 diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index a3a841dc6..05a7f4ddf 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -235,6 +235,31 @@ var _ = Describe("Podman generate kube", func() { } }) + It("podman generate kube on pod with memory limit", func() { + podName := "testMemoryLimit" + podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName}) + podSession.WaitWithDefaultTimeout() + Expect(podSession.ExitCode()).To(Equal(0)) + + ctr1Name := "ctr1" + ctr1Session := podmanTest.Podman([]string{"create", "--name", ctr1Name, "--pod", podName, "--memory", "10Mi", ALPINE, "top"}) + ctr1Session.WaitWithDefaultTimeout() + Expect(ctr1Session.ExitCode()).To(Equal(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", podName}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + pod := new(v1.Pod) + err := yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + + for _, ctr := range pod.Spec.Containers { + memoryLimit, _ := ctr.Resources.Limits.Memory().AsInt64() + Expect(memoryLimit).To(Equal(int64(10 * 1024 * 1024))) + } + }) + It("podman generate kube on pod with ports", func() { podName := "test" podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName, "-p", "4000:4000", "-p", "5000:5000"}) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index bcbfdd80a..49f5f0ce6 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -50,15 +50,17 @@ var _ = Describe("Podman Info", func() { {"{{ json .}}", true, 0}, {"{{json . }}", true, 0}, {" {{ json . }} ", true, 0}, - {"{{json }}", false, 125}, + {"{{json }}", true, 0}, {"{{json .", false, 125}, - {"json . }}", false, 0}, // Note: this does NOT fail but produces garbage + {"json . }}", false, 0}, // without opening {{ template seen as string literal } for _, tt := range tests { session := podmanTest.Podman([]string{"info", "--format", tt.input}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(tt.exitCode)) - Expect(session.IsJSONOutputValid()).To(Equal(tt.success)) + + desc := fmt.Sprintf("JSON test(%q)", tt.input) + Expect(session).Should(Exit(tt.exitCode), desc) + Expect(session.IsJSONOutputValid()).To(Equal(tt.success), desc) } }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 3aa3cf409..67ab71d20 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -203,6 +203,7 @@ var _ = Describe("Podman logs", func() { results.WaitWithDefaultTimeout() Expect(results).To(Exit(0)) Expect(len(results.OutputToStringArray())).To(Equal(3)) + Expect(results.OutputToString()).To(Equal("podman podman podman")) }) It("using journald tail two lines", func() { diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index 515391f92..063c71b9f 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -225,7 +225,6 @@ var _ = Describe("Podman pod create", func() { }) It("podman pod container can override pod pid NS", func() { - SkipIfRemote("FIXME This should work on podman-remote") session := podmanTest.Podman([]string{"pod", "create", "--share", "pid"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -257,7 +256,6 @@ var _ = Describe("Podman pod create", func() { }) It("podman pod container can override pod not sharing pid", func() { - SkipIfRemote("FIXME This should work on podman-remote") session := podmanTest.Podman([]string{"pod", "create", "--share", "net"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -283,7 +281,6 @@ var _ = Describe("Podman pod create", func() { }) It("podman pod container can override pod ipc NS", func() { - SkipIfRemote("FIXME This should work on podman-remote") session := podmanTest.Podman([]string{"pod", "create", "--share", "ipc"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 82a842146..0f2ce2d46 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -173,6 +173,18 @@ var _ = Describe("Podman ps", func() { Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0)) }) + It("podman ps namespace flag even for remote", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + + result := podmanTest.Podman([]string{"ps", "-a", "--namespace", "--format", + "{{with .Namespaces}}{{.Cgroup}}:{{.IPC}}:{{.MNT}}:{{.NET}}:{{.PIDNS}}:{{.User}}:{{.UTS}}{{end}}"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + // it must contains `::` when some ns is null. If it works normally, it should be "$num1:$num2:$num3" + Expect(result.OutputToString()).To(Not(ContainSubstring(`::`))) + }) + It("podman ps with no containers is valid json format", func() { result := podmanTest.Podman([]string{"ps", "--format", "json"}) result.WaitWithDefaultTimeout() diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go index 219750bcb..3294f6d3b 100644 --- a/test/e2e/run_selinux_test.go +++ b/test/e2e/run_selinux_test.go @@ -182,4 +182,115 @@ var _ = Describe("Podman run", func() { match2, _ := session.GrepString("s0:c1,c2") Expect(match2).To(BeTrue()) }) + + It("podman pod container share SELinux labels", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podID := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + label1 := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(label1)) + + session = podmanTest.Podman([]string{"pod", "rm", podID, "--force"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman pod container --infra=false doesn't share SELinux labels", func() { + session := podmanTest.Podman([]string{"pod", "create", "--infra=false"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podID := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + label1 := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Not(Equal(label1))) + + session = podmanTest.Podman([]string{"pod", "rm", podID, "--force"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman shared IPC NS container share SELinux labels", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"exec", "test1", "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + label1 := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--ipc", "container:test1", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(label1)) + }) + + It("podman shared PID NS container share SELinux labels", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"exec", "test1", "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + label1 := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--pid", "container:test1", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(label1)) + }) + + It("podman shared NET NS container doesn't share SELinux labels", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"exec", "test1", "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + label1 := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "--net", "container:test1", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Not(Equal(label1))) + }) + + It("podman test --pid=host", func() { + session := podmanTest.Podman([]string{"run", "--pid=host", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("spc_t")) + }) + + It("podman test --ipc=host", func() { + session := podmanTest.Podman([]string{"run", "--ipc=host", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("spc_t")) + }) + + It("podman test --ipc=net", func() { + session := podmanTest.Podman([]string{"run", "--net=host", ALPINE, "cat", "/proc/self/attr/current"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("container_t")) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 292df529c..05aede122 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -67,6 +67,30 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run --rm with --restart", func() { + session := podmanTest.Podman([]string{"run", "--rm", "--restart", "", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--rm", "--restart", "no", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // the --rm option conflicts with --restart, when the restartPolicy is not "" and "no" + // so the exitCode should not equal 0 + session = podmanTest.Podman([]string{"run", "--rm", "--restart", "on-failure", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + + session = podmanTest.Podman([]string{"run", "--rm", "--restart", "always", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + + session = podmanTest.Podman([]string{"run", "--rm", "--restart", "unless-stopped", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + It("podman run a container based on on a short name with localhost", func() { tag := podmanTest.Podman([]string{"tag", nginx, "localhost/libpod/alpine_nginx:latest"}) tag.WaitWithDefaultTimeout() diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 695cccc11..903748b58 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "os" . "github.com/containers/podman/v2/test/utils" @@ -68,15 +69,17 @@ var _ = Describe("Podman version", func() { {"{{ json .}}", true, 0}, {"{{json . }}", true, 0}, {" {{ json . }} ", true, 0}, - {"{{json }}", false, 125}, + {"{{json }}", true, 0}, {"{{json .", false, 125}, - {"json . }}", false, 0}, // Note: this does NOT fail but produces garbage + {"json . }}", false, 0}, // without opening {{ template seen as string literal } for _, tt := range tests { session := podmanTest.Podman([]string{"version", "--format", tt.input}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(tt.exitCode)) - Expect(session.IsJSONOutputValid()).To(Equal(tt.success)) + + desc := fmt.Sprintf("JSON test(%q)", tt.input) + Expect(session).Should(Exit(tt.exitCode), desc) + Expect(session.IsJSONOutputValid()).To(Equal(tt.success), desc) } }) diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 1d5eb066b..d276cfda1 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -61,7 +61,7 @@ function setup() { is "$output" ".*Server:" "podman --remote: contacts server" # This was failing: "podman --foo --bar --remote". - PODMAN="${podman_non_remote} --tmpdir /var/tmp --log-level=error ${podman_args[@]} --remote" run_podman version + PODMAN="${podman_non_remote} --log-level=error ${podman_args[@]} --remote" run_podman version is "$output" ".*Server:" "podman [flags] --remote: contacts server" # ...but no matter what, --remote is never allowed after subcommand diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index cbb2091e5..130bc5243 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -50,4 +50,15 @@ ${cid[1]} c ${cid[0]} d" "Sequential output from logs" } +@test "podman logs over journald" { + msg=$(random_string 20) + + run_podman run --name myctr --log-driver journald $IMAGE echo $msg + + run_podman logs myctr + is "$output" "$msg" "check that log output equals the container output" + + run_podman rm myctr +} + # vim: filetype=sh |