mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-10-11 00:17:41 +00:00
ps: fix label lookup in container and pod table headings
Table headings for `podman ps` and `podman pod ps` use a plain
`map[string]string`. A table template that calls `.Label` with an
argument fails while rendering the headings, before listing rows
are printed. For example: `podman ps --format 'table {{.Label "app"}}'`
Add a shared `PsHeader` type in `cmd/podman/common` with a `Label`
method that returns the requested label name as the column heading.
Use this type for both container and pod listings.
Add container unit tests and extend the existing container and pod
e2e tests to check label headings, label values, missing labels,
unlabeled objects, and `--noheading` output.
Fixes: #29831
Signed-off-by: Garv Changrani <154041471+Garvity@users.noreply.github.com>
This commit is contained in:
parent
a1ce95d761
commit
d2b8502869
6 changed files with 102 additions and 11 deletions
|
|
@ -19,3 +19,11 @@ func FormatLabels(labels map[string]string) string {
|
|||
}
|
||||
return strings.Join(list, ",")
|
||||
}
|
||||
|
||||
// PsHeader provides column headings for container and pod listings.
|
||||
type PsHeader map[string]string
|
||||
|
||||
// Label uses the requested label name as the column heading.
|
||||
func (h PsHeader) Label(name string) string {
|
||||
return name
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ func ps(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
// cannot use report.Headers() as it doesn't support structures as fields
|
||||
func createPsOut() ([]map[string]string, string) {
|
||||
func createPsOut() ([]common.PsHeader, string) {
|
||||
hdrs := report.Headers(psReporter{}, map[string]string{
|
||||
"Cgroup": "cgroupns",
|
||||
"CreatedHuman": "created",
|
||||
|
|
@ -327,7 +327,7 @@ func createPsOut() ([]map[string]string, string) {
|
|||
row += "\t{{.Size}}"
|
||||
}
|
||||
}
|
||||
return hdrs, "{{range .}}" + row + "\n{{end -}}"
|
||||
return []common.PsHeader{common.PsHeader(hdrs[0])}, "{{range .}}" + row + "\n{{end -}}"
|
||||
}
|
||||
|
||||
type psReporter struct {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,70 @@
|
|||
package containers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.podman.io/common/libnetwork/types"
|
||||
"go.podman.io/common/pkg/report"
|
||||
"go.podman.io/podman/v6/pkg/domain/entities"
|
||||
)
|
||||
|
||||
func TestPsLabelFormat(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
format string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "table labels",
|
||||
format: `table {{.Names}}|{{.Label "app"}}|{{.Label "com.example.project"}}|{{.Label "missing"}}`,
|
||||
want: "NAMES|app|com.example.project|missing\nwith-label|my-app|my-project|\nwithout-label|||\n",
|
||||
},
|
||||
{
|
||||
name: "labels without headings",
|
||||
format: `{{.Names}}|{{.Label "app"}}`,
|
||||
want: "with-label|my-app\nwithout-label|\n",
|
||||
},
|
||||
{
|
||||
name: "ordinary headings",
|
||||
format: `table {{.Names}}|{{.ID}}`,
|
||||
want: "NAMES|CONTAINER ID\nwith-label|123456789abc\nwithout-label|abcdef123456\n",
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
rpt, err := report.New(&output, "ps").Parse(report.OriginUser, tt.format)
|
||||
require.NoError(t, err)
|
||||
if rpt.RenderHeaders {
|
||||
headers, _ := createPsOut()
|
||||
require.NoError(t, rpt.Execute(headers))
|
||||
}
|
||||
require.NoError(t, rpt.Execute([]psReporter{
|
||||
{
|
||||
ListContainer: entities.ListContainer{
|
||||
ID: "123456789abc",
|
||||
Names: []string{"with-label"},
|
||||
Labels: map[string]string{
|
||||
"app": "my-app",
|
||||
"com.example.project": "my-project",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ListContainer: entities.ListContainer{
|
||||
ID: "abcdef123456",
|
||||
Names: []string{"without-label"},
|
||||
},
|
||||
},
|
||||
}))
|
||||
require.NoError(t, rpt.Flush())
|
||||
assert.Equal(t, tt.want, output.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_portsToString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func pods(cmd *cobra.Command, _ []string) error {
|
|||
"Restarts": "RESTARTS",
|
||||
})
|
||||
|
||||
if err := rpt.Execute(headers); err != nil {
|
||||
if err := rpt.Execute([]common.PsHeader{common.PsHeader(headers[0])}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,19 +352,32 @@ var _ = Describe("Podman ps", func() {
|
|||
})
|
||||
|
||||
It("podman pod ps format with labels", func() {
|
||||
_, ec, _ := podmanTest.CreatePod(nil)
|
||||
_, ec, _ := podmanTest.CreatePod(map[string][]string{"--name": {"without-label"}})
|
||||
Expect(ec).To(Equal(0))
|
||||
|
||||
_, ec1, _ := podmanTest.CreatePod(map[string][]string{"--label": {
|
||||
"io.podman.test.label=value1",
|
||||
"io.podman.test.key=irrelevant-value",
|
||||
}})
|
||||
_, ec1, _ := podmanTest.CreatePod(map[string][]string{
|
||||
"--name": {"with-label"},
|
||||
"--label": {
|
||||
"io.podman.test.label=value1",
|
||||
"io.podman.test.key=irrelevant-value",
|
||||
},
|
||||
})
|
||||
Expect(ec1).To(Equal(0))
|
||||
|
||||
session := podmanTest.Podman([]string{"pod", "ps", "--format", "{{.Labels}}"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
Expect(session.OutputToString()).To(ContainSubstring("value1"))
|
||||
|
||||
format := `table {{.Name}}|{{.Label "io.podman.test.label"}}|{{.Label "missing"}}`
|
||||
session = podmanTest.PodmanExitCleanly("pod", "ps", "--format", format)
|
||||
actual := session.OutputToStringArray()
|
||||
Expect(actual).To(HaveLen(3))
|
||||
Expect(actual[0]).To(Equal("NAME|io.podman.test.label|missing"))
|
||||
Expect(actual[1:]).To(ConsistOf("with-label|value1|", "without-label||"))
|
||||
|
||||
session = podmanTest.PodmanExitCleanly("pod", "ps", "--noheading", "--format", format)
|
||||
Expect(session.OutputToStringArray()).To(ConsistOf("with-label|value1|", "without-label||"))
|
||||
})
|
||||
|
||||
It("podman pod ps headers", func() {
|
||||
|
|
|
|||
|
|
@ -298,10 +298,11 @@ var _ = Describe("Podman ps", func() {
|
|||
})
|
||||
|
||||
It("podman ps namespace flag with go template format", func() {
|
||||
_, ec, _ := podmanTest.RunLsContainer("test1")
|
||||
Expect(ec).To(Equal(0))
|
||||
result := podmanTest.Podman([]string{"run", "--name", "test1", "--network=none", "--label", "app=my-app", ALPINE, "ls"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result).Should(Exit(0))
|
||||
|
||||
result := podmanTest.Podman([]string{"ps", "-a", "--format", "table {{.ID}} {{.Image}} {{.ImageID}} {{.Labels}}"})
|
||||
result = podmanTest.Podman([]string{"ps", "-a", "--format", "table {{.ID}} {{.Image}} {{.ImageID}} {{.Labels}}"})
|
||||
result.WaitWithDefaultTimeout()
|
||||
Expect(result).Should(ExitCleanly())
|
||||
|
||||
|
|
@ -311,6 +312,17 @@ var _ = Describe("Podman ps", func() {
|
|||
Expect(actual[0]).To(ContainSubstring("CONTAINER ID"))
|
||||
Expect(actual[0]).ToNot(ContainSubstring("ImageID"))
|
||||
Expect(actual[1]).To(ContainSubstring("alpine:latest"))
|
||||
|
||||
podmanTest.PodmanExitCleanly("create", "--name", "test2", ALPINE, "ls")
|
||||
format := `table {{.Names}}|{{.Label "app"}}|{{.Label "missing"}}`
|
||||
result = podmanTest.PodmanExitCleanly("ps", "-a", "--format", format)
|
||||
actual = result.OutputToStringArray()
|
||||
Expect(actual).To(HaveLen(3))
|
||||
Expect(actual[0]).To(Equal("NAMES|app|missing"))
|
||||
Expect(actual[1:]).To(ConsistOf("test1|my-app|", "test2||"))
|
||||
|
||||
result = podmanTest.PodmanExitCleanly("ps", "-a", "--noheading", "--format", format)
|
||||
Expect(result.OutputToStringArray()).To(ConsistOf("test1|my-app|", "test2||"))
|
||||
})
|
||||
|
||||
It("podman ps ancestor filter flag", func() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue