summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/ps.go53
-rw-r--r--cmd/podman/containers/rm.go13
-rw-r--r--cmd/podman/generate/systemd.go7
-rw-r--r--cmd/podman/images/search.go12
-rw-r--r--cmd/podman/networks/inspect.go38
-rw-r--r--cmd/podman/pods/ps.go108
-rw-r--r--cmd/podman/pods/stats.go94
-rw-r--r--cmd/podman/root.go19
-rw-r--r--cmd/podman/root_test.go34
-rw-r--r--cmd/podman/volumes/list.go54
-rw-r--r--completions/bash/podman1
-rw-r--r--docs/source/Introduction.rst4
-rw-r--r--docs/source/markdown/podman-rm.1.md9
-rw-r--r--docs/source/markdown/podman-search.1.md18
-rw-r--r--libpod/image/search.go51
-rw-r--r--libpod/networking_linux.go11
-rw-r--r--pkg/api/handlers/compat/containers.go3
-rw-r--r--pkg/api/handlers/compat/events.go3
-rw-r--r--pkg/api/handlers/libpod/images.go7
-rw-r--r--pkg/api/server/register_images.go4
-rw-r--r--pkg/bindings/images/images.go1
-rw-r--r--pkg/domain/entities/containers.go1
-rw-r--r--pkg/domain/entities/images.go4
-rw-r--r--pkg/domain/infra/abi/containers.go28
-rw-r--r--pkg/domain/infra/abi/images.go2
-rw-r--r--pkg/spec/config_linux_cgo.go2
-rw-r--r--test/apiv2/12-imagesMore.at44
-rw-r--r--test/apiv2/20-containers.at2
-rw-r--r--test/e2e/network_test.go39
-rw-r--r--test/e2e/play_kube_test.go2
-rw-r--r--test/e2e/pod_ps_test.go3
-rw-r--r--test/e2e/pod_stats_test.go5
-rw-r--r--test/e2e/ps_test.go12
-rw-r--r--test/e2e/rm_test.go1
-rw-r--r--test/e2e/run_test.go2
-rw-r--r--test/e2e/search_test.go20
-rw-r--r--test/e2e/volume_ls_test.go7
-rw-r--r--test/system/010-images.bats52
-rw-r--r--test/system/055-rm.bats15
-rw-r--r--test/system/060-mount.bats13
-rw-r--r--test/system/130-kill.bats20
-rw-r--r--test/system/410-selinux.bats108
-rw-r--r--test/system/helpers.bash11
43 files changed, 663 insertions, 274 deletions
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index c4c8b60f3..8082a74c2 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -12,7 +12,9 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/buildah/pkg/formats"
+ "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/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -176,47 +178,51 @@ func ps(cmd *cobra.Command, args []string) error {
return err
}
}
- if listOpts.Format == "json" {
+
+ switch {
+ case parse.MatchesJSONFormat(listOpts.Format):
return jsonOut(listContainers)
- }
- if listOpts.Quiet {
+ case listOpts.Quiet:
return quietOut(listContainers)
}
+ // Output table Watch > 0 will refresh screen
responses := make([]psReporter, 0, len(listContainers))
for _, r := range listContainers {
responses = append(responses, psReporter{r})
}
- headers, format := createPsOut()
- if cmd.Flag("format").Changed {
- format = strings.TrimPrefix(listOpts.Format, "table ")
- if !strings.HasPrefix(format, "\n") {
- format += "\n"
- }
- }
- format = "{{range . }}" + format + "{{end}}"
- if !listOpts.Quiet && !cmd.Flag("format").Changed {
- format = headers + format
+ var headers, format string
+ if cmd.Flags().Changed("format") {
+ headers = ""
+ format = report.NormalizeFormat(listOpts.Format)
+ } else {
+ headers, format = createPsOut()
}
+ format = headers + "{{range . }}" + format + "{{end}}"
+
tmpl, err := template.New("listContainers").Parse(format)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
+
if listOpts.Watch > 0 {
for {
var responses []psReporter
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
- listContainers, err := getResponses()
- for _, r := range listContainers {
- responses = append(responses, psReporter{r})
- }
- if err != nil {
+
+ if ctnrs, err := getResponses(); err != nil {
return err
+ } else {
+ for _, r := range ctnrs {
+ responses = append(responses, psReporter{r})
+ }
}
+
if err := tmpl.Execute(w, responses); err != nil {
return err
}
@@ -232,11 +238,11 @@ func ps(cmd *cobra.Command, args []string) error {
if err := tmpl.Execute(w, responses); err != nil {
return err
}
- return w.Flush()
}
return nil
}
+// cannot use report.Headers() as it doesn't support structures as fields
func createPsOut() (string, string) {
var row string
if listOpts.Namespace {
@@ -257,12 +263,9 @@ func createPsOut() (string, string) {
headers += "\tSIZE"
row += "\t{{.Size}}"
}
- if !strings.HasSuffix(headers, "\n") {
- headers += "\n"
- }
- if !strings.HasSuffix(row, "\n") {
- row += "\n"
- }
+
+ headers = report.NormalizeFormat(headers)
+ row = report.NormalizeFormat(row)
return headers, row
}
diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go
index f8f12234d..a7739b3ba 100644
--- a/cmd/podman/containers/rm.go
+++ b/cmd/podman/containers/rm.go
@@ -57,13 +57,12 @@ func rmFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
- flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library")
flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container")
flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
- if registry.IsRemote() {
- _ = flags.MarkHidden("ignore")
- _ = flags.MarkHidden("cidfile")
+ if !registry.IsRemote() {
+ // This option is deprecated, but needs to still exists for backwards compatibility
+ flags.Bool("storage", false, "Remove container from storage library")
_ = flags.MarkHidden("storage")
}
}
@@ -97,12 +96,6 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit
var (
errs utils.OutputErrors
)
- // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore
- if rmOptions.Storage {
- if rmOptions.All || rmOptions.Ignore || rmOptions.Latest || rmOptions.Volumes || rmOptions.CIDFiles != nil {
- return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile")
- }
- }
responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions)
if err != nil {
if setExit {
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
index f690836a4..02e826549 100644
--- a/cmd/podman/generate/systemd.go
+++ b/cmd/podman/generate/systemd.go
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -97,10 +98,10 @@ func systemd(cmd *cobra.Command, args []string) error {
}
}
- switch format {
- case "json":
+ switch {
+ case parse.MatchesJSONFormat(format):
return printJSON(report.Units)
- case "":
+ case format == "":
return printDefault(report.Units)
default:
return errors.Errorf("unknown --format argument: %s", format)
diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go
index b8f590585..8edd776ce 100644
--- a/cmd/podman/images/search.go
+++ b/cmd/podman/images/search.go
@@ -85,6 +85,7 @@ func searchFlags(flags *pflag.FlagSet) {
flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output")
flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
+ flags.BoolVar(&searchOptions.ListTags, "list-tags", false, "List the tags of the input registry")
}
// imageSearch implements the command for searching images.
@@ -101,6 +102,10 @@ func imageSearch(cmd *cobra.Command, args []string) error {
return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit)
}
+ if searchOptions.ListTags && len(searchOptions.Filters) != 0 {
+ return errors.Errorf("filters are not applicable to list tags result")
+ }
+
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
@@ -119,12 +124,19 @@ func imageSearch(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+
if len(searchReport) == 0 {
return nil
}
hdrs := report.Headers(entities.ImageSearchReport{}, nil)
row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n"
+ if searchOptions.ListTags {
+ if len(searchOptions.Filters) != 0 {
+ return errors.Errorf("filters are not applicable to list tags result")
+ }
+ row = "{{.Name}}\t{{.Tag}}\n"
+ }
if cmd.Flags().Changed("format") {
row = report.NormalizeFormat(searchOptions.Format)
}
diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go
index c5872def7..c36125948 100644
--- a/cmd/podman/networks/inspect.go
+++ b/cmd/podman/networks/inspect.go
@@ -3,12 +3,13 @@ package network
import (
"encoding/json"
"fmt"
- "io"
"os"
- "strings"
+ "text/tabwriter"
"text/template"
+ "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/spf13/cobra"
)
@@ -39,31 +40,32 @@ func init() {
flags.StringVarP(&networkInspectOptions.Format, "format", "f", "", "Pretty-print network to JSON or using a Go template")
}
-func networkInspect(cmd *cobra.Command, args []string) error {
+func networkInspect(_ *cobra.Command, args []string) error {
responses, err := registry.ContainerEngine().NetworkInspect(registry.Context(), args, entities.NetworkInspectOptions{})
if err != nil {
return err
}
- b, err := json.MarshalIndent(responses, "", " ")
- if err != nil {
- return err
- }
- if strings.ToLower(networkInspectOptions.Format) == "json" || networkInspectOptions.Format == "" {
- fmt.Println(string(b))
- } else {
- var w io.Writer = os.Stdout
- //There can be more than 1 in the inspect output.
- format := "{{range . }}" + networkInspectOptions.Format + "{{end}}"
- tmpl, err := template.New("inspectNetworks").Parse(format)
+
+ switch {
+ case parse.MatchesJSONFormat(networkInspectOptions.Format) || networkInspectOptions.Format == "":
+ b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
- if err := tmpl.Execute(w, responses); err != nil {
+ fmt.Println(string(b))
+ default:
+ row := report.NormalizeFormat(networkInspectOptions.Format)
+ // There can be more than 1 in the inspect output.
+ row = "{{range . }}" + row + "{{end}}"
+ tmpl, err := template.New("inspectNetworks").Parse(row)
+ if err != nil {
return err
}
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
- }
+
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 0, ' ', 0)
+ defer w.Flush()
+
+ return tmpl.Execute(w, responses)
}
return nil
}
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 7b755cb22..b7952e6e3 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -3,7 +3,6 @@ package pods
import (
"context"
"fmt"
- "io"
"os"
"sort"
"strings"
@@ -11,7 +10,9 @@ import (
"text/template"
"time"
+ "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"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
@@ -34,10 +35,9 @@ var (
)
var (
- defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED"
- inputFilters []string
- noTrunc bool
- psInput entities.PodPSOptions
+ inputFilters []string
+ noTrunc bool
+ psInput entities.PodPSOptions
)
func init() {
@@ -62,11 +62,6 @@ func init() {
}
func pods(cmd *cobra.Command, _ []string) error {
- var (
- w io.Writer = os.Stdout
- row string
- )
-
if psInput.Quiet && len(psInput.Format) > 0 {
return errors.New("quiet and format cannot be used together")
}
@@ -89,80 +84,79 @@ func pods(cmd *cobra.Command, _ []string) error {
return err
}
- if psInput.Format == "json" {
+ switch {
+ case parse.MatchesJSONFormat(psInput.Format):
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
+ case psInput.Quiet:
+ for _, p := range responses {
+ fmt.Println(p.Id)
+ }
+ return nil
}
+ // Formatted output below
lpr := make([]ListPodReporter, 0, len(responses))
for _, r := range responses {
lpr = append(lpr, ListPodReporter{r})
}
- headers, row := createPodPsOut()
- if psInput.Quiet {
- row = "{{.Id}}\n"
- }
- if cmd.Flag("format").Changed {
- row = psInput.Format
- if !strings.HasPrefix(row, "\n") {
- row += "\n"
- }
- }
- format := "{{range . }}" + row + "{{end}}"
- if !psInput.Quiet && !cmd.Flag("format").Changed {
- format = headers + format
+
+ headers := report.Headers(ListPodReporter{}, map[string]string{
+ "ContainerIds": "IDS",
+ "ContainerNames": "NAMES",
+ "ContainerStatuses": "STATUS",
+ "Namespace": "NAMESPACES",
+ "NumberOfContainers": "# OF CONTAINERS",
+ "InfraId": "INFRA ID",
+ })
+ row := podPsFormat()
+ if cmd.Flags().Changed("format") {
+ row = report.NormalizeFormat(psInput.Format)
}
- tmpl, err := template.New("listPods").Parse(format)
+ row = "{{range . }}" + row + "{{end}}"
+
+ tmpl, err := template.New("listPods").Parse(row)
if err != nil {
return err
}
- if !psInput.Quiet {
- w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
- }
- if err := tmpl.Execute(w, lpr); err != nil {
- return err
- }
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
+
+ if !psInput.Quiet && !cmd.Flag("format").Changed {
+ if err := tmpl.Execute(w, headers); err != nil {
+ return err
+ }
}
- return nil
+ return tmpl.Execute(w, lpr)
}
-func createPodPsOut() (string, string) {
- var row string
- headers := defaultHeaders
- row += "{{.Id}}"
-
- row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}"
+func podPsFormat() string {
+ row := []string{"{{.Id}}", "{{.Name}}", "{{.Status}}", "{{.Created}}}"}
if psInput.CtrIds {
- headers += "\tIDS"
- row += "\t{{.ContainerIds}}"
+ row = append(row, "{{.ContainerIds}}")
}
+
if psInput.CtrNames {
- headers += "\tNAMES"
- row += "\t{{.ContainerNames}}"
+ row = append(row, "{{.ContainerNames}}")
}
+
if psInput.CtrStatus {
- headers += "\tSTATUS"
- row += "\t{{.ContainerStatuses}}"
+ row = append(row, "{{.ContainerStatuses}}")
}
+
if psInput.Namespace {
- headers += "\tCGROUP\tNAMESPACES"
- row += "\t{{.Cgroup}}\t{{.Namespace}}"
+ row = append(row, "{{.Cgroup}}", "{{.Namespace}}")
}
- if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
- headers += "\t# OF CONTAINERS"
- row += "\t{{.NumberOfContainers}}"
+ if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
+ row = append(row, "{{.NumberOfContainers}}")
}
- headers += "\tINFRA ID\n"
- row += "\t{{.InfraId}}\n"
- return headers, row
+ return strings.Join(row, "\t") + "\n"
}
// ListPodReporter is a struct for pod ps output
@@ -180,7 +174,7 @@ func (l ListPodReporter) Labels() map[string]string {
return l.ListPodsReport.Labels
}
-// NumberofContainers returns an int representation for
+// NumberOfContainers returns an int representation for
// the number of containers belonging to the pod
func (l ListPodReporter) NumberOfContainers() int {
return len(l.Containers)
@@ -192,7 +186,7 @@ func (l ListPodReporter) ID() string {
}
// Id returns the Pod id
-func (l ListPodReporter) Id() string { //nolint
+func (l ListPodReporter) Id() string { // nolint
if noTrunc {
return l.ListPodsReport.Id
}
@@ -206,7 +200,7 @@ func (l ListPodReporter) InfraID() string {
// InfraId returns the infra container id for the pod
// depending on trunc
-func (l ListPodReporter) InfraId() string { //nolint
+func (l ListPodReporter) InfraId() string { // nolint
if len(l.ListPodsReport.InfraId) == 0 {
return ""
}
diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go
index 1d916dbfa..2f59e4e47 100644
--- a/cmd/podman/pods/stats.go
+++ b/cmd/podman/pods/stats.go
@@ -4,18 +4,16 @@ import (
"context"
"fmt"
"os"
- "reflect"
- "strings"
"text/tabwriter"
"text/template"
"time"
"github.com/buger/goterm"
- "github.com/containers/buildah/pkg/formats"
+ "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"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/util/camelcase"
"github.com/spf13/cobra"
)
@@ -67,11 +65,18 @@ func stats(cmd *cobra.Command, args []string) error {
return err
}
- format := statsOptions.Format
- doJSON := strings.ToLower(format) == formats.JSONString
- header := getPodStatsHeader(format)
+ row := report.NormalizeFormat(statsOptions.Format)
+ doJSON := parse.MatchesJSONFormat(row)
- for {
+ headers := report.Headers(entities.PodStatsReport{}, map[string]string{
+ "CPU": "CPU %",
+ "MemUsage": "MEM USAGE/ LIMIT",
+ "MEM": "MEM %",
+ "NET IO": "NET IO",
+ "BlockIO": "BLOCK IO",
+ })
+
+ for ; ; time.Sleep(time.Second) {
reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions)
if err != nil {
return err
@@ -87,16 +92,17 @@ func stats(cmd *cobra.Command, args []string) error {
goterm.MoveCursor(1, 1)
goterm.Flush()
}
- if len(format) == 0 {
+ if cmd.Flags().Changed("format") {
+ if err := printFormattedPodStatsLines(headers, row, reports); err != nil {
+ return err
+ }
+ } else {
printPodStatsLines(reports)
- } else if err := printFormattedPodStatsLines(format, reports, header); err != nil {
- return err
}
}
if statsOptions.NoStream {
break
}
- time.Sleep(time.Second)
}
return nil
@@ -115,72 +121,32 @@ func printPodStatsLines(stats []*entities.PodStatsReport) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
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")
- for _, i := range stats {
- if len(stats) == 0 {
- fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
- } else {
+ if len(stats) == 0 {
+ fmt.Fprintf(w, outFormat, "--", "--", "--", "--", "--", "--", "--", "--", "--")
+ } else {
+ for _, i := range stats {
fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
}
}
w.Flush()
}
-func printFormattedPodStatsLines(format string, stats []*entities.PodStatsReport, headerNames map[string]string) error {
+func printFormattedPodStatsLines(headerNames []map[string]string, row string, stats []*entities.PodStatsReport) error {
if len(stats) == 0 {
return nil
}
- // Use a tabwriter to align column format
- w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
- // Spit out the header if "table" is present in the format
- if strings.HasPrefix(format, "table") {
- hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
- format = hformat
- headerTmpl, err := template.New("header").Parse(hformat)
- if err != nil {
- return err
- }
- if err := headerTmpl.Execute(w, headerNames); err != nil {
- return err
- }
- fmt.Fprintln(w, "")
- }
+ row = "{{range .}}" + row + "{{end}}"
- // Spit out the data rows now
- dataTmpl, err := template.New("data").Parse(format)
+ tmpl, err := template.New("pod stats").Parse(row)
if err != nil {
return err
}
- for _, s := range stats {
- if err := dataTmpl.Execute(w, s); err != nil {
- return err
- }
- fmt.Fprintln(w, "")
- }
- // Flush the writer
- return w.Flush()
-
-}
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
+ defer w.Flush()
-// getPodStatsHeader returns the stats header for the specified options.
-func getPodStatsHeader(format string) map[string]string {
- headerNames := make(map[string]string)
- if format == "" {
- return headerNames
- }
- // Make a map of the field names for the headers
- v := reflect.ValueOf(entities.PodStatsReport{})
- t := v.Type()
- for i := 0; i < t.NumField(); i++ {
- split := camelcase.Split(t.Field(i).Name)
- value := strings.ToUpper(strings.Join(split, " "))
- switch value {
- case "CPU", "MEM":
- value += " %"
- case "MEM USAGE":
- value = "MEM USAGE / LIMIT"
- }
- headerNames[t.Field(i).Name] = value
+ if err := tmpl.Execute(w, headerNames); err != nil {
+ return err
}
- return headerNames
+ return tmpl.Execute(w, stats)
}
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 1e73f7540..6293fa17d 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/validate"
+ "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/parallel"
"github.com/containers/podman/v2/pkg/rootless"
@@ -84,7 +85,7 @@ func init() {
func Execute() {
if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil {
- fmt.Fprintln(os.Stderr, "Error:", err.Error())
+ fmt.Fprintln(os.Stderr, formatError(err))
} else if registry.GetExitCode() == registry.ExecErrorCodeGeneric {
// The exitCode modified from registry.ExecErrorCodeGeneric,
// indicates an application
@@ -331,3 +332,19 @@ func resolveDestination() (string, string, string) {
}
return cfg.Engine.ActiveService, uri, ident
}
+
+func formatError(err error) string {
+ var message string
+ if errors.Cause(err) == define.ErrOCIRuntime {
+ // OCIRuntimeErrors include the reason for the failure in the
+ // second to last message in the error chain.
+ message = fmt.Sprintf(
+ "Error: %s: %s",
+ define.ErrOCIRuntime.Error(),
+ strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()),
+ )
+ } else {
+ message = "Error: " + err.Error()
+ }
+ return message
+}
diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go
new file mode 100644
index 000000000..0473128df
--- /dev/null
+++ b/cmd/podman/root_test.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/pkg/errors"
+)
+
+func TestFormatError(t *testing.T) {
+ err := errors.New("unknown error")
+ output := formatError(err)
+ expected := fmt.Sprintf("Error: %v", err)
+
+ if output != expected {
+ t.Errorf("Expected \"%s\" to equal \"%s\"", output, err.Error())
+ }
+}
+
+func TestFormatOCIError(t *testing.T) {
+ expectedPrefix := "Error: "
+ expectedSuffix := "OCI runtime output"
+ err := errors.Wrap(define.ErrOCIRuntime, expectedSuffix)
+ output := formatError(err)
+
+ if !strings.HasPrefix(output, expectedPrefix) {
+ t.Errorf("Expected \"%s\" to start with \"%s\"", output, expectedPrefix)
+ }
+ if !strings.HasSuffix(output, expectedSuffix) {
+ t.Errorf("Expected \"%s\" to end with \"%s\"", output, expectedSuffix)
+ }
+}
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index d198e51a7..18765a499 100644
--- a/cmd/podman/volumes/list.go
+++ b/cmd/podman/volumes/list.go
@@ -3,13 +3,14 @@ package volumes
import (
"context"
"fmt"
- "io"
"os"
"strings"
"text/tabwriter"
"text/template"
+ "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"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@@ -55,7 +56,6 @@ func init() {
}
func list(cmd *cobra.Command, args []string) error {
- var w io.Writer = os.Stdout
if cliOpts.Quiet && cmd.Flag("format").Changed {
return errors.New("quiet and format flags cannot be used together")
}
@@ -73,40 +73,40 @@ func list(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- if cliOpts.Format == "json" {
- return outputJSON(responses)
- }
- if len(responses) < 1 {
+ switch {
+ case parse.MatchesJSONFormat(cliOpts.Format):
+ return outputJSON(responses)
+ case len(responses) < 1:
return nil
}
- // "\t" from the command line is not being recognized as a tab
- // replacing the string "\t" to a tab character if the user passes in "\t"
- cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1)
+ return outputTemplate(cmd, responses)
+}
+
+func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) error {
+ headers := report.Headers(entities.VolumeListReport{}, map[string]string{
+ "Name": "VOLUME NAME",
+ })
+
+ row := report.NormalizeFormat(cliOpts.Format)
if cliOpts.Quiet {
- cliOpts.Format = "{{.Name}}\n"
+ row = "{{.Name}}\n"
}
- headers := "DRIVER\tVOLUME NAME\n"
- row := cliOpts.Format
- if !strings.HasSuffix(cliOpts.Format, "\n") {
- row += "\n"
- }
- format := "{{range . }}" + row + "{{end}}"
- if !cliOpts.Quiet && !cmd.Flag("format").Changed {
- w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
- format = headers + format
- }
- tmpl, err := template.New("listVolume").Parse(format)
+ row = "{{range . }}" + row + "{{end}}"
+
+ tmpl, err := template.New("list volume").Parse(row)
if err != nil {
return err
}
- if err := tmpl.Execute(w, responses); err != nil {
- return err
- }
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
+ w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
+ defer w.Flush()
+
+ if !cliOpts.Quiet && !cmd.Flag("format").Changed {
+ if err := tmpl.Execute(w, headers); err != nil {
+ return errors.Wrapf(err, "failed to write report column headers")
+ }
}
- return nil
+ return tmpl.Execute(w, responses)
}
func outputJSON(vols []*entities.VolumeListReport) error {
diff --git a/completions/bash/podman b/completions/bash/podman
index e12862126..564d35f67 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -2024,6 +2024,7 @@ _podman_search() {
--help
-h
--no-trunc
+ --list-tags
"
_complete_ "$options_with_args" "$boolean_options"
}
diff --git a/docs/source/Introduction.rst b/docs/source/Introduction.rst
index a1f9d605e..9dcae8a83 100644
--- a/docs/source/Introduction.rst
+++ b/docs/source/Introduction.rst
@@ -100,7 +100,7 @@ To summarize, Podman makes it easy to find, run, build and share containers.
* Find: whether finding a container on dockerhub.io or quay.io, an internal registry server, or directly from a vendor, a couple of `podman search`_, and `podman pull`_ commands make it easy
* Run: it's easy to consume pre-built images with everything needed to run an entire application, or start from a Linux distribution base image with the `podman run`_ command
-* Build: creating new layers with small tweaks, or major overhauls is easy with `podman build`
-* Share: Podman let’s you push your newly built containers anywhere you want with a single `podman push`_ command
+* Build: creating new layers with small tweaks, or major overhauls is easy with `podman build`_
+* Share: Podman lets you push your newly built containers anywhere you want with a single `podman push`_ command
For more instructions on use cases, take a look at our :doc:`Tutorials` page.
diff --git a/docs/source/markdown/podman-rm.1.md b/docs/source/markdown/podman-rm.1.md
index e3e6740df..36904a128 100644
--- a/docs/source/markdown/podman-rm.1.md
+++ b/docs/source/markdown/podman-rm.1.md
@@ -43,13 +43,6 @@ to run containers such as CRI-O, the last started container could be from either
The latest option is not supported on the remote client.
-**--storage**
-
-Remove external containers from the storage library.
-This is only possible with containers that are not present in libpod can be seen by **podman ps --all --storage**).
-It is used to remove external containers from **podman build** and **buildah**, and orphan containers which were only partially removed by **podman rm**.
-The storage option conflicts with the **--all**, **--latest**, and **--volumes** options.
-
**--volumes**, **-v**
Remove anonymous volumes associated with the container. This does not include named volumes
@@ -96,7 +89,7 @@ $ podman rm -f --latest
**125** The command fails for any other reason
## SEE ALSO
-podman(1), podman-image-rm(1), podman-ps(1), podman-build(1)
+podman(1), podman-image-rm(1), podman-ps(1), podman-build(1), buildah(1), cri-o(1)
## HISTORY
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
diff --git a/docs/source/markdown/podman-search.1.md b/docs/source/markdown/podman-search.1.md
index 2c2a8f012..fc09d96ea 100644
--- a/docs/source/markdown/podman-search.1.md
+++ b/docs/source/markdown/podman-search.1.md
@@ -56,6 +56,9 @@ Valid placeholders for the Go template are listed below:
| .Stars | Star count of image |
| .Official | "[OK]" if image is official |
| .Automated | "[OK]" if image is automated |
+| .Tag | Repository tag |
+
+Note: use .Tag only if the --list-tags is set.
**--limit**=*limit*
@@ -65,6 +68,12 @@ Example if limit is 10 and two registries are being searched, the total
number of results will be 20, 10 from each (if there are at least 10 matches in each).
The order of the search results is the order in which the API endpoint returns the results.
+**--list-tags**
+
+List the available tags in the repository for the specified image.
+**Note:** --list-tags requires the search term to be a fully specified image name.
+The result contains the Image name and its tag, one line for every tag associated with the image.
+
**--no-trunc**
Do not truncate the output
@@ -140,6 +149,15 @@ fedoraproject.org registry.fedoraproject.org/f25/kubernetes-proxy
fedoraproject.org registry.fedoraproject.org/f25/kubernetes-scheduler 0
fedoraproject.org registry.fedoraproject.org/f25/mariadb 0
```
+
+```
+$ podman search --list-tags registry.redhat.io/rhel
+NAME TAG
+registry.redhat.io/rhel 7.3-74
+registry.redhat.io/rhel 7.6-301
+registry.redhat.io/rhel 7.1-9
+...
+```
Note: This works only with registries that implement the v2 API. If tried with a v1 registry an error will be returned.
## FILES
diff --git a/libpod/image/search.go b/libpod/image/search.go
index 6bcc6d3f8..5f5845989 100644
--- a/libpod/image/search.go
+++ b/libpod/image/search.go
@@ -2,11 +2,13 @@ package image
import (
"context"
+ "fmt"
"strconv"
"strings"
"sync"
"github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
sysreg "github.com/containers/podman/v2/pkg/registries"
"github.com/pkg/errors"
@@ -34,6 +36,8 @@ type SearchResult struct {
Official string
// Automated indicates if the image was created by an automated build.
Automated string
+ // Tag is the image tag
+ Tag string
}
// SearchOptions are used to control the behaviour of SearchImages.
@@ -49,6 +53,8 @@ type SearchOptions struct {
Authfile string
// InsecureSkipTLSVerify allows to skip TLS verification.
InsecureSkipTLSVerify types.OptionalBool
+ // ListTags returns the search result with available tags
+ ListTags bool
}
// SearchFilter allows filtering the results of SearchImages.
@@ -147,6 +153,15 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
// every types.SystemContext, and to compute the value just once in one
// place.
sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath()
+ if options.ListTags {
+ results, err := searchRepositoryTags(registry, term, sc, options)
+ if err != nil {
+ logrus.Errorf("error listing registry tags %q: %v", registry, err)
+ return []SearchResult{}
+ }
+ return results
+ }
+
results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit)
if err != nil {
logrus.Errorf("error searching registry %q: %v", registry, err)
@@ -207,6 +222,42 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
return paramsArr
}
+func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) {
+ dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+ imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term))
+ if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
+ return nil, errors.Errorf("reference %q must be a docker reference", term)
+ } else if err != nil {
+ imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term)))
+ if err != nil {
+ return nil, errors.Errorf("reference %q must be a docker reference", term)
+ }
+ }
+ tags, err := docker.GetRepositoryTags(context.TODO(), sc, imageRef)
+ if err != nil {
+ return nil, errors.Errorf("error getting repository tags: %v", err)
+ }
+ limit := maxQueries
+ if len(tags) < limit {
+ limit = len(tags)
+ }
+ if options.Limit != 0 {
+ limit = len(tags)
+ if options.Limit < limit {
+ limit = options.Limit
+ }
+ }
+ paramsArr := []SearchResult{}
+ for i := 0; i < limit; i++ {
+ params := SearchResult{
+ Name: imageRef.DockerReference().Name(),
+ Tag: tags[i],
+ }
+ paramsArr = append(paramsArr, params)
+ }
+ return paramsArr, nil
+}
+
// ParseSearchFilter turns the filter into a SearchFilter that can be used for
// searching images.
func ParseSearchFilter(filter []string) (*SearchFilter, error) {
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index d16bdc973..f87c311ce 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -828,6 +828,17 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
// We can't do more if the network is down.
if c.state.NetNS == nil {
+ // We still want to make dummy configurations for each CNI net
+ // the container joined.
+ if len(c.config.Networks) > 0 {
+ settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(c.config.Networks))
+ for _, net := range c.config.Networks {
+ cniNet := new(define.InspectAdditionalNetwork)
+ cniNet.NetworkID = net
+ settings.Networks[net] = cniNet
+ }
+ }
+
return settings, nil
}
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 48ecfff5d..cae8f88fd 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"syscall"
+ "time"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
@@ -316,7 +317,7 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
cb := types.ContainerJSONBase{
ID: l.ID(),
- Created: l.CreatedTime().String(),
+ Created: l.CreatedTime().Format(time.RFC3339Nano),
Path: "",
Args: nil,
State: &state,
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 9efdd1261..a729b84d4 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -139,7 +139,8 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
+ case <-r.Context().Done():
+ return
}
-
}
}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 43123c5a3..1292090fb 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -608,6 +608,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
NoTrunc bool `json:"noTrunc"`
Filters []string `json:"filters"`
TLSVerify bool `json:"tlsVerify"`
+ ListTags bool `json:"listTags"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -618,8 +619,9 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
}
options := image.SearchOptions{
- Limit: query.Limit,
- NoTrunc: query.NoTrunc,
+ Limit: query.Limit,
+ NoTrunc: query.NoTrunc,
+ ListTags: query.ListTags,
}
if _, found := r.URL.Query()["tlsVerify"]; found {
options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
@@ -650,6 +652,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
reports[i].Stars = searchResults[i].Stars
reports[i].Official = searchResults[i].Official
reports[i].Automated = searchResults[i].Automated
+ reports[i].Tag = searchResults[i].Tag
}
utils.WriteResponse(w, http.StatusOK, reports)
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index ad779203d..c2423218a 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -169,6 +169,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - `is-automated=(true|false)`
// - `is-official=(true|false)`
// - `stars=<number>` Matches images that has at least 'number' stars.
+ // - in: query
+ // name: listTags
+ // type: boolean
+ // description: list the available tags in the repository
// produces:
// - application/json
// responses:
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index a78e7f4c6..2d3035d8d 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -314,6 +314,7 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
params.Set("term", term)
params.Set("limit", strconv.Itoa(opts.Limit))
params.Set("noTrunc", strconv.FormatBool(opts.NoTrunc))
+ params.Set("listTags", strconv.FormatBool(opts.ListTags))
for _, f := range opts.Filters {
params.Set("filters", f)
}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 7b272f01e..3b6dd106f 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -131,7 +131,6 @@ type RmOptions struct {
Force bool
Ignore bool
Latest bool
- Storage bool
Volumes bool
}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index ac81c282d..982fa0cc0 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -214,6 +214,8 @@ type ImageSearchOptions struct {
NoTrunc bool
// SkipTLSVerify to skip HTTPS and certificate verification.
SkipTLSVerify types.OptionalBool
+ // ListTags search the available tags of the repository
+ ListTags bool
}
// ImageSearchReport is the response from searching images.
@@ -230,6 +232,8 @@ type ImageSearchReport struct {
Official string
// Automated indicates if the image was created by an automated build.
Automated string
+ // Tag is the repository tag
+ Tag string
}
// Image List Options
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 0107e18c4..ac7523094 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -273,16 +273,6 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st
func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, options entities.RmOptions) ([]*entities.RmReport, error) {
reports := []*entities.RmReport{}
- if options.Storage {
- for _, ctr := range namesOrIds {
- report := entities.RmReport{Id: ctr}
- if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
- report.Err = err
- }
- reports = append(reports, &report)
- }
- return reports, nil
- }
names := namesOrIds
for _, cidFile := range options.CIDFiles {
@@ -294,6 +284,22 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
names = append(names, id)
}
+ // Attempt to remove named containers directly from storage, if container is defined in libpod
+ // this will fail and code will fall through to removing the container from libpod.`
+ tmpNames := []string{}
+ for _, ctr := range names {
+ report := entities.RmReport{Id: ctr}
+ if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
+ // remove container names that we successfully deleted
+ tmpNames = append(tmpNames, ctr)
+ } else {
+ reports = append(reports, &report)
+ }
+ }
+ if len(tmpNames) < len(names) {
+ names = tmpNames
+ }
+
ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod)
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
// Failed to get containers. If force is specified, get the containers ID
@@ -302,7 +308,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
return nil, err
}
- for _, ctr := range namesOrIds {
+ for _, ctr := range names {
logrus.Debugf("Evicting container %q", ctr)
report := entities.RmReport{Id: ctr}
id, err := ic.Libpod.EvictContainer(ctx, ctr, options.Volumes)
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 3bb7de83c..f9d733c63 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -511,6 +511,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Limit: opts.Limit,
NoTrunc: opts.NoTrunc,
InsecureSkipTLSVerify: opts.SkipTLSVerify,
+ ListTags: opts.ListTags,
}
searchResults, err := image.SearchImages(term, searchOpts)
@@ -529,6 +530,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
reports[i].Stars = searchResults[i].Stars
reports[i].Official = searchResults[i].Official
reports[i].Automated = searchResults[i].Automated
+ reports[i].Tag = searchResults[i].Tag
}
return reports, nil
diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go
index bc8fc4e29..d0891b574 100644
--- a/pkg/spec/config_linux_cgo.go
+++ b/pkg/spec/config_linux_cgo.go
@@ -39,7 +39,7 @@ func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.Linu
logrus.Debug("Loading default seccomp profile")
seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec)
if err != nil {
- return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ return nil, errors.Wrapf(err, "loading default seccomp profile failed")
}
}
diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at
new file mode 100644
index 000000000..30ccf0cfc
--- /dev/null
+++ b/test/apiv2/12-imagesMore.at
@@ -0,0 +1,44 @@
+# -*- sh -*-
+#
+# Tests for more image-related endpoints
+#
+
+podman pull -q $IMAGE
+
+t GET libpod/images/json 200 \
+ .[0].Id~[0-9a-f]\\{64\\}
+iid=$(jq -r '.[0].Id' <<<"$output")
+
+# Retrieve the image tree
+t GET libpod/images/$IMAGE/tree 200 \
+ .Tree~^Image
+
+# Tag nonesuch image
+t POST "libpod/images/nonesuch/tag?repo=myrepo&tag=mytag" '' 404
+
+# Tag the image
+t POST "libpod/images/$IMAGE/tag?repo=localhost:5000/myrepo&tag=mytag" '' 201
+
+t GET libpod/images/$IMAGE/json 200 \
+ .RepoTags[1]=localhost:5000/myrepo:mytag
+
+# Run registry container
+podman run -d --name registry -p 5000:5000 docker.io/library/registry:2.6 /entrypoint.sh /etc/docker/registry/config.yml
+
+# Push to local registry
+t POST libpod/images/localhost:5000/myrepo:mytag/push\?tlsVerify\=false '' 200
+
+# Untag the image
+t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" '' 201
+
+t GET libpod/images/$IMAGE/json 200 \
+ .RepoTags[-1]=$IMAGE
+
+# Remove the registry container
+t DELETE libpod/containers/registry?force=true 204
+
+# Remove images
+t DELETE libpod/images/$IMAGE 200 \
+ .ExitCode=0
+t DELETE libpod/images/docker.io/library/registry:2.6 200 \
+ .ExitCode=0
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 28289955a..d7e5bfee8 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -206,7 +206,7 @@ t POST containers/${cid_top}/stop "" 204
t DELETE containers/$cid 204
t DELETE containers/$cid_top 204
-# test the apiv2 create, should't ignore the ENV and WORKDIR from the image
+# test the apiv2 create, shouldn't ignore the ENV and WORKDIR from the image
t POST containers/create '"Image":"'$ENV_WORKDIR_IMG'","Env":["testKey1"]' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index a15359ea3..9bd16c008 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -28,7 +28,7 @@ func removeConf(confPath string) {
// generateNetworkConfig generates a cni config with a random name
// it returns the network name and the filepath
func generateNetworkConfig(p *PodmanTestIntegration) (string, string) {
- // generate a random name to preven conflicts with other tests
+ // generate a random name to prevent conflicts with other tests
name := "net" + stringid.GenerateNonCryptoID()
path := filepath.Join(p.CNIConfigDir, fmt.Sprintf("%s.conflist", name))
conf := fmt.Sprintf(`{
@@ -211,6 +211,43 @@ var _ = Describe("Podman network", func() {
Expect(rmAll.ExitCode()).To(BeZero())
})
+ It("podman inspect container two CNI networks (container not running)", func() {
+ netName1 := "testNetThreeCNI1"
+ network1 := podmanTest.Podman([]string{"network", "create", netName1})
+ network1.WaitWithDefaultTimeout()
+ Expect(network1.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName1)
+
+ netName2 := "testNetThreeCNI2"
+ network2 := podmanTest.Podman([]string{"network", "create", netName2})
+ network2.WaitWithDefaultTimeout()
+ Expect(network2.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName2)
+
+ ctrName := "testCtr"
+ container := podmanTest.Podman([]string{"create", "--network", fmt.Sprintf("%s,%s", netName1, netName2), "--name", ctrName, ALPINE, "top"})
+ container.WaitWithDefaultTimeout()
+ Expect(container.ExitCode()).To(BeZero())
+
+ inspect := podmanTest.Podman([]string{"inspect", ctrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(BeZero())
+ conData := inspect.InspectContainerToJSON()
+ Expect(len(conData)).To(Equal(1))
+ Expect(len(conData[0].NetworkSettings.Networks)).To(Equal(2))
+ net1, ok := conData[0].NetworkSettings.Networks[netName1]
+ Expect(ok).To(BeTrue())
+ Expect(net1.NetworkID).To(Equal(netName1))
+ net2, ok := conData[0].NetworkSettings.Networks[netName2]
+ Expect(ok).To(BeTrue())
+ Expect(net2.NetworkID).To(Equal(netName2))
+
+ // Necessary to ensure the CNI network is removed cleanly
+ rmAll := podmanTest.Podman([]string{"rm", "-f", ctrName})
+ rmAll.WaitWithDefaultTimeout()
+ Expect(rmAll.ExitCode()).To(BeZero())
+ })
+
It("podman inspect container two CNI networks", func() {
netName1 := "testNetTwoCNI1"
network1 := podmanTest.Podman([]string{"network", "create", "--subnet", "10.50.51.0/25", netName1})
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index b0831c823..b6a390950 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -769,7 +769,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test restartPolicy", func() {
// podName, set, expect
testSli := [][]string{
- {"testPod1", "", "always"}, // Default eqaul to always
+ {"testPod1", "", "always"}, // Default equal to always
{"testPod2", "Always", "always"},
{"testPod3", "OnFailure", "on-failure"},
{"testPod4", "Never", "no"},
diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go
index 17ed6a9c0..a299d3cf2 100644
--- a/test/e2e/pod_ps_test.go
+++ b/test/e2e/pod_ps_test.go
@@ -8,6 +8,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman ps", func() {
@@ -63,7 +64,7 @@ var _ = Describe("Podman ps", func() {
result := podmanTest.Podman([]string{"pod", "ps", "-q"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).To(Exit(0))
Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0))
Expect(podid).To(ContainSubstring(result.OutputToStringArray()[0]))
})
diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go
index 1ffbe282b..41fc59267 100644
--- a/test/e2e/pod_stats_test.go
+++ b/test/e2e/pod_stats_test.go
@@ -6,6 +6,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman pod stats", func() {
@@ -156,9 +157,9 @@ var _ = Describe("Podman pod stats", func() {
session := podmanTest.RunTopContainerInPod("", podid)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}\""})
+ stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}"})
stats.WaitWithDefaultTimeout()
- Expect(stats.ExitCode()).To(Equal(0))
+ Expect(stats).To(Exit(0))
})
It("podman stats with invalid GO template", func() {
diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go
index 0f2ce2d46..48ef566ce 100644
--- a/test/e2e/ps_test.go
+++ b/test/e2e/ps_test.go
@@ -11,6 +11,7 @@ import (
"github.com/docker/go-units"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman ps", func() {
@@ -218,17 +219,16 @@ var _ = Describe("Podman ps", func() {
})
It("podman ps namespace flag with go template format", func() {
- Skip("FIXME: table still not supported in podman ps command")
_, ec, _ := podmanTest.RunLsContainer("test1")
Expect(ec).To(Equal(0))
result := podmanTest.Podman([]string{"ps", "-a", "--format", "table {{.ID}} {{.Image}} {{.ImageID}} {{.Labels}}"})
result.WaitWithDefaultTimeout()
- Expect(strings.Contains(result.OutputToStringArray()[0], "table")).To(BeFalse())
- Expect(strings.Contains(result.OutputToStringArray()[0], "ID")).To(BeTrue())
- Expect(strings.Contains(result.OutputToStringArray()[0], "ImageID")).To(BeTrue())
- Expect(strings.Contains(result.OutputToStringArray()[1], "alpine:latest")).To(BeTrue())
- Expect(result.ExitCode()).To(Equal(0))
+
+ Expect(result.OutputToStringArray()[0]).ToNot(ContainSubstring("table"))
+ Expect(result.OutputToStringArray()[0]).ToNot(ContainSubstring("ImageID"))
+ Expect(result.OutputToStringArray()[0]).To(ContainSubstring("alpine:latest"))
+ Expect(result).Should(Exit(0))
})
It("podman ps ancestor filter flag", func() {
diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go
index 7eff8c6ed..524c07cc6 100644
--- a/test/e2e/rm_test.go
+++ b/test/e2e/rm_test.go
@@ -236,7 +236,6 @@ var _ = Describe("Podman rm", func() {
})
It("podman rm --ignore bogus container and a running container", func() {
-
session := podmanTest.RunTopContainer("test1")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index cd32e5a77..e6bba9f67 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -394,7 +394,7 @@ USER bin`
})
It("podman run sysctl test", func() {
- SkipIfRootless("Network sysctls are not avalable root rootless")
+ SkipIfRootless("Network sysctls are not available root rootless")
session := podmanTest.Podman([]string{"run", "--rm", "--sysctl", "net.core.somaxconn=65535", ALPINE, "sysctl", "net.core.somaxconn"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go
index 043da9059..0cf005529 100644
--- a/test/e2e/search_test.go
+++ b/test/e2e/search_test.go
@@ -423,4 +423,24 @@ registries = ['{{.Host}}:{{.Port}}']`
Expect(search.ExitCode()).To(Equal(0))
Expect(len(search.OutputToStringArray()) > 1).To(BeTrue())
})
+
+ It("podman search repository tags", func() {
+ search := podmanTest.Podman([]string{"search", "--list-tags", "--limit", "30", "docker.io/library/alpine"})
+ search.WaitWithDefaultTimeout()
+ Expect(search.ExitCode()).To(Equal(0))
+ Expect(len(search.OutputToStringArray())).To(Equal(31))
+
+ search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/alpine"})
+ search.WaitWithDefaultTimeout()
+ Expect(search.ExitCode()).To(Equal(0))
+ Expect(len(search.OutputToStringArray()) > 2).To(BeTrue())
+
+ search = podmanTest.Podman([]string{"search", "--filter=is-official", "--list-tags", "docker.io/library/alpine"})
+ search.WaitWithDefaultTimeout()
+ Expect(search.ExitCode()).To(Not(Equal(0)))
+
+ search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/"})
+ search.WaitWithDefaultTimeout()
+ Expect(len(search.OutputToStringArray()) == 0).To(BeTrue())
+ })
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index 4a2c2d324..1cb6440aa 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -7,6 +7,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman volume ls", func() {
@@ -56,15 +57,15 @@ var _ = Describe("Podman volume ls", func() {
})
It("podman ls volume with Go template", func() {
- Skip("FIXME: table still not supported in podman volume command")
session := podmanTest.Podman([]string{"volume", "create", "myvol"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"volume", "ls", "--format", "table {{.Name}} {{.Driver}} {{.Scope}}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToStringArray())).To(Equal(2))
+
+ Expect(session).Should(Exit(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(1), session.OutputToString())
})
It("podman ls volume with --filter flag", func() {
diff --git a/test/system/010-images.bats b/test/system/010-images.bats
index ac65e54d9..900a24368 100644
--- a/test/system/010-images.bats
+++ b/test/system/010-images.bats
@@ -159,4 +159,56 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
is "$output" "$images_baseline" "after podman rmi @sha, still the same"
}
+# Tests #7199 (Restore "table" --format from V1)
+#
+# Tag our image with different-length strings; confirm table alignment
+@test "podman images - table format" {
+ # Craft two tags such that they will bracket $IMAGE on either side (above
+ # and below). This assumes that $IMAGE is quay.io or foo.com or simply
+ # not something insane that will sort before 'aaa' or after 'zzz'.
+ local aaa_name=a.b/c
+ local aaa_tag=d
+ local zzz_name=zzzzzzzzzz.yyyyyyyyy/xxxxxxxxx
+ local zzz_tag=$(random_string 15)
+
+ # Helper function to check one line of tabular output; all this does is
+ # generate a line with the given repo/tag, formatted to the width of the
+ # widest image, which is the zzz one. Fields are separated by TWO spaces.
+ function _check_line() {
+ local lineno=$1
+ local name=$2
+ local tag=$3
+
+ is "${lines[$lineno]}" \
+ "$(printf '%-*s %-*s %s' ${#zzz_name} ${name} ${#zzz_tag} ${tag} $iid)" \
+ "podman images, $testname, line $lineno"
+ }
+
+ function _run_format_test() {
+ local testname=$1
+ local format=$2
+
+ run_podman images --sort repository --format "$format"
+ _check_line 0 ${aaa_name} ${aaa_tag}
+ _check_line 1 "${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/${PODMAN_TEST_IMAGE_NAME}" "${PODMAN_TEST_IMAGE_TAG}"
+ _check_line 2 ${zzz_name} ${zzz_tag}
+ }
+
+ # Begin the test: tag $IMAGE with both the given names
+ run_podman tag $IMAGE ${aaa_name}:${aaa_tag}
+ run_podman tag $IMAGE ${zzz_name}:${zzz_tag}
+
+ # Get the image ID, used to verify output below (all images share same IID)
+ run_podman inspect --format '{{.ID}}' $IMAGE
+ iid=${output:0:12}
+
+ # Run the test: this will output three column-aligned rows. Test them.
+ # Tab character (\t) should have the same effect as the 'table' directive
+ _run_format_test 'table' 'table {{.Repository}} {{.Tag}} {{.ID}}'
+ _run_format_test 'tabs' '{{.Repository}}\t{{.Tag}}\t{{.ID}}'
+
+ # Clean up.
+ run_podman rmi ${aaa_name}:${aaa_tag} ${zzz_name}:${zzz_tag}
+}
+
# vim: filetype=sh
diff --git a/test/system/055-rm.bats b/test/system/055-rm.bats
index c8475c3e9..7176ae4b8 100644
--- a/test/system/055-rm.bats
+++ b/test/system/055-rm.bats
@@ -33,6 +33,21 @@ load helpers
run_podman rm -f $cid
}
+@test "podman rm container from storage" {
+ if is_remote; then
+ skip "only applicable for local podman"
+ fi
+ rand=$(random_string 30)
+ run_podman create --name $rand $IMAGE /bin/true
+
+ # Create a container that podman does not know about
+ run buildah from $IMAGE
+ cid="$output"
+
+ # rm should succeed
+ run_podman rm $rand $cid
+}
+
# I'm sorry! This test takes 13 seconds. There's not much I can do about it,
# please know that I think it's justified: podman 1.5.0 had a strange bug
# in with exit status was not preserved on some code paths with 'rm -f'
diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats
index f11aff773..ece87acf6 100644
--- a/test/system/060-mount.bats
+++ b/test/system/060-mount.bats
@@ -43,6 +43,11 @@ load helpers
# Start with clean slate
run_podman image umount -a
+ # Get full image ID, to verify umount
+ run_podman image inspect --format '{{.ID}}' $IMAGE
+ iid="$output"
+
+ # Mount, and make sure the mount point exists
run_podman image mount $IMAGE
mount_path="$output"
@@ -60,6 +65,14 @@ load helpers
# Clean up
run_podman image umount $IMAGE
+ is "$output" "$iid" "podman image umount: image ID of what was umounted"
+
+ run_podman image umount $IMAGE
+ is "$output" "" "podman image umount: does not re-umount"
+
+ run_podman 125 image umount no-such-container
+ is "$output" "Error: unable to find a name and tag match for no-such-container in repotags: no such image" \
+ "error message from image umount no-such-container"
run_podman image mount
is "$output" "" "podman image mount, no args, after umount"
diff --git a/test/system/130-kill.bats b/test/system/130-kill.bats
index c16e64c58..3770eac27 100644
--- a/test/system/130-kill.bats
+++ b/test/system/130-kill.bats
@@ -6,23 +6,9 @@
load helpers
@test "podman kill - test signal handling in containers" {
- # podman-remote and crun interact poorly in f31: crun seems to gobble up
- # some signals.
- # Workaround: run 'env --default-signal sh' instead of just 'sh' in
- # the container. Since env on our regular alpine image doesn't support
- # that flag, we need to pull fedora-minimal. See:
- # https://github.com/containers/podman/issues/5004
- # FIXME: remove this kludge once we get rid of podman-remote
- local _image=$IMAGE
- local _sh_cmd="sh"
- if is_remote; then
- _image=quay.io/libpod/fedora-minimal:latest
- _sh_cmd="env --default-signal sh"
- fi
-
# Start a container that will handle all signals by emitting 'got: N'
local -a signals=(1 2 3 4 5 6 8 10 12 13 14 15 16 20 21 22 23 24 25 26 64)
- run_podman run -d $_image $_sh_cmd -c \
+ run_podman run -d $IMAGE sh -c \
"for i in ${signals[*]}; do trap \"echo got: \$i\" \$i; done;
echo READY;
while ! test -e /stop; do sleep 0.05; done;
@@ -81,10 +67,6 @@ load helpers
run_podman wait $cid
run_podman rm $cid
wait $podman_log_pid
-
- if [[ $_image != $IMAGE ]]; then
- run_podman rmi $_image
- fi
}
@test "podman kill - rejects invalid args" {
diff --git a/test/system/410-selinux.bats b/test/system/410-selinux.bats
index 497e29b3e..1e44fe06c 100644
--- a/test/system/410-selinux.bats
+++ b/test/system/410-selinux.bats
@@ -7,9 +7,7 @@ load helpers
function check_label() {
- if [ ! -e /usr/sbin/selinuxenabled ] || ! /usr/sbin/selinuxenabled; then
- skip "selinux disabled or not available"
- fi
+ skip_if_no_selinux
local args="$1"; shift # command-line args for run
@@ -52,15 +50,33 @@ function check_label() {
check_label "--privileged --userns=host" "spc_t"
}
+@test "podman selinux: pid=host" {
+ # FIXME FIXME FIXME: Remove these lines once all VMs have >= 2.146.0
+ # (this is ugly, but better than an unconditional skip)
+ skip_if_no_selinux
+ if is_rootless; then
+ if [ -x /usr/bin/rpm ]; then
+ cs_version=$(rpm -q --qf '%{version}' container-selinux)
+ else
+ # SELinux not enabled on Ubuntu, so we should never get here
+ die "WHOA! SELinux enabled, but no /usr/bin/rpm!"
+ fi
+ if [[ "$cs_version" < "2.146" ]]; then
+ skip "FIXME: #7939: requires container-selinux-2.146.0 (currently installed: $cs_version)"
+ fi
+ fi
+ # FIXME FIXME FIXME: delete up to here, leaving just check_label
+
+ check_label "--pid=host" "spc_t"
+}
+
@test "podman selinux: container with overridden range" {
check_label "--security-opt label=level:s0:c1,c2" "container_t" "s0:c1,c2"
}
# pr #6752
@test "podman selinux: inspect multiple labels" {
- if [ ! -e /usr/sbin/selinuxenabled ] || ! /usr/sbin/selinuxenabled; then
- skip "selinux disabled or not available"
- fi
+ skip_if_no_selinux
run_podman run -d --name myc \
--security-opt seccomp=unconfined \
@@ -75,4 +91,84 @@ function check_label() {
run_podman rm -f myc
}
+# Sharing context between two containers not in a pod
+# These tests were piggybacked in with #7902, but are not actually related
+@test "podman selinux: shared context in (some) namespaces" {
+ skip_if_no_selinux
+
+ run_podman run -d --name myctr $IMAGE top
+ run_podman exec myctr cat -v /proc/self/attr/current
+ context_c1="$output"
+
+ # --ipc container
+ run_podman run --name myctr2 --ipc container:myctr $IMAGE cat -v /proc/self/attr/current
+ is "$output" "$context_c1" "new container, run with ipc of existing one "
+
+ # --pid container
+ run_podman run --rm --pid container:myctr $IMAGE cat -v /proc/self/attr/current
+ is "$output" "$context_c1" "new container, run with --pid of existing one "
+
+ # net NS: do not share context
+ run_podman run --rm --net container:myctr $IMAGE cat -v /proc/self/attr/current
+ if [[ "$output" = "$context_c1" ]]; then
+ die "run --net : context ($output) is same as running container (it should not be)"
+ fi
+
+ # The 'myctr2' above was not run with --rm, so it still exists, and
+ # we can't remove the original container until this one is gone.
+ run_podman stop -t 0 myctr
+ run_podman 125 rm myctr
+ is "$output" "Error: container .* has dependent containers"
+
+ # We have to do this in two steps: even if ordered as 'myctr2 myctr',
+ # podman will try the removes in random order, which fails if it
+ # tries myctr first.
+ run_podman rm myctr2
+ run_podman rm myctr
+}
+
+# pr #7902 - containers in pods should all run under same context
+@test "podman selinux: containers in pods share full context" {
+ skip_if_no_selinux
+
+ # We don't need a fullblown pause container; avoid pulling the k8s one
+ run_podman pod create --name myselinuxpod \
+ --infra-image $IMAGE \
+ --infra-command /home/podman/pause
+
+ # Get baseline
+ run_podman run --rm --pod myselinuxpod $IMAGE cat -v /proc/self/attr/current
+ context_c1="$output"
+
+ # Prior to #7902, the labels (':c123,c456') would be different
+ run_podman run --rm --pod myselinuxpod $IMAGE cat -v /proc/self/attr/current
+ is "$output" "$context_c1" "SELinux context of 2nd container matches 1st"
+
+ # What the heck. Try a third time just for extra confidence
+ run_podman run --rm --pod myselinuxpod $IMAGE cat -v /proc/self/attr/current
+ is "$output" "$context_c1" "SELinux context of 3rd container matches 1st"
+
+ run_podman pod rm myselinuxpod
+}
+
+# more pr #7902
+@test "podman selinux: containers in --no-infra pods do not share context" {
+ skip_if_no_selinux
+
+ # We don't need a fullblown pause container; avoid pulling the k8s one
+ run_podman pod create --name myselinuxpod --infra=false
+
+ # Get baseline
+ run_podman run --rm --pod myselinuxpod $IMAGE cat -v /proc/self/attr/current
+ context_c1="$output"
+
+ # Even after #7902, labels (':c123,c456') should be different
+ run_podman run --rm --pod myselinuxpod $IMAGE cat -v /proc/self/attr/current
+ if [[ "$output" = "$context_c1" ]]; then
+ die "context ($output) is the same on two separate containers, it should have been different"
+ fi
+
+ run_podman pod rm myselinuxpod
+}
+
# vim: filetype=sh
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 998db5283..c6c2c12df 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -286,6 +286,17 @@ function skip_if_remote() {
fi
}
+########################
+# skip_if_no_selinux #
+########################
+function skip_if_no_selinux() {
+ if [ ! -e /usr/sbin/selinuxenabled ]; then
+ skip "selinux not available"
+ elif ! /usr/sbin/selinuxenabled; then
+ skip "selinux disabled"
+ fi
+}
+
#########
# die # Abort with helpful message
#########