Skip to content

Commit fe91b75

Browse files
committed
add filter for container command
Signed-off-by: Oleksandr Krutko <[email protected]> add a test, improve logic of command filter Signed-off-by: Oleksandr Krutko <[email protected]> improve a test Signed-off-by: Oleksandr Krutko <[email protected]> improve test, update a man page Signed-off-by: Oleksandr Krutko <[email protected]> improve man page, runtime functions Signed-off-by: Oleksandr Krutko <[email protected]>
1 parent 7b35f4f commit fe91b75

File tree

7 files changed

+139
-8
lines changed

7 files changed

+139
-8
lines changed

Diff for: cmd/podman/common/completion.go

+36
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,41 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s
314314
return suggestions, cobra.ShellCompDirectiveNoFileComp
315315
}
316316

317+
func getCommands(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
318+
suggestions := []string{}
319+
lsOpts := entities.ContainerListOptions{}
320+
321+
engine, err := setupContainerEngine(cmd)
322+
if err != nil {
323+
cobra.CompErrorln(err.Error())
324+
return nil, cobra.ShellCompDirectiveNoFileComp
325+
}
326+
327+
containers, err := engine.ContainerList(registry.GetContext(), lsOpts)
328+
if err != nil {
329+
cobra.CompErrorln(err.Error())
330+
return nil, cobra.ShellCompDirectiveNoFileComp
331+
}
332+
333+
externalContainers, err := engine.ContainerListExternal(registry.GetContext())
334+
if err != nil {
335+
cobra.CompErrorln(err.Error())
336+
return nil, cobra.ShellCompDirectiveNoFileComp
337+
}
338+
containers = append(containers, externalContainers...)
339+
340+
for _, container := range containers {
341+
// taking of the first element of commands list was done intentionally
342+
// to exclude command arguments from suggestions (e.g. exclude arguments "-g daemon"
343+
// from "nginx -g daemon" output)
344+
if strings.HasPrefix(container.Command[0], toComplete) {
345+
suggestions = append(suggestions, container.Command[0])
346+
}
347+
}
348+
349+
return suggestions, cobra.ShellCompDirectiveNoFileComp
350+
}
351+
317352
func fdIsNotDir(f *os.File) bool {
318353
stat, err := f.Stat()
319354
if err != nil {
@@ -1658,6 +1693,7 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string)
16581693
kv := keyValueCompletion{
16591694
"ancestor=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
16601695
"before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
1696+
"command=": func(s string) ([]string, cobra.ShellCompDirective) { return getCommands(cmd, s) },
16611697
"exited=": nil,
16621698
"health=": func(_ string) ([]string, cobra.ShellCompDirective) {
16631699
return []string{define.HealthCheckHealthy,

Diff for: docs/source/markdown/podman-ps.1.md

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Valid filters are listed below:
6161
| pod | [Pod] name or full or partial ID of pod |
6262
| network | [Network] name or full ID of network |
6363
| until | [DateTime] container created before the given duration or time. |
64+
| command | [Command] the command the container is executing, only argv[0] is taken |
65+
6466

6567

6668
#### **--format**=*format*

Diff for: libpod/runtime_ctr.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/containers/podman/v5/libpod/define"
2222
"github.com/containers/podman/v5/libpod/events"
2323
"github.com/containers/podman/v5/libpod/shutdown"
24+
contEntities "github.com/containers/podman/v5/pkg/domain/entities"
2425
"github.com/containers/podman/v5/pkg/domain/entities/reports"
2526
"github.com/containers/podman/v5/pkg/rootless"
2627
"github.com/containers/podman/v5/pkg/specgen"
@@ -44,6 +45,11 @@ type CtrCreateOption func(*Container) error
4445
// A true return will include the container, a false return will exclude it.
4546
type ContainerFilter func(*Container) bool
4647

48+
// ExternalContainerFilter is a function to determine whether a container list is included
49+
// in command output. Container lists to be outputted are tested using the function.
50+
// A true return will include the container list, a false return will exclude it.
51+
type ExternalContainerFilter func(*contEntities.ListContainer) bool
52+
4753
// NewContainer creates a new container from a given OCI config.
4854
func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, spec *specgen.SpecGenerator, infra bool, options ...CtrCreateOption) (*Container, error) {
4955
if !r.valid {
@@ -1246,9 +1252,16 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
12461252
return nil, err
12471253
}
12481254

1249-
ctrsFiltered := make([]*Container, 0, len(ctrs))
1255+
ctrsFiltered := applyContainersFilters(ctrs, filters...)
1256+
1257+
return ctrsFiltered, nil
1258+
}
1259+
1260+
// Applies container filters on bunch of containers
1261+
func applyContainersFilters(containers []*Container, filters ...ContainerFilter) []*Container {
1262+
ctrsFiltered := make([]*Container, 0, len(containers))
12501263

1251-
for _, ctr := range ctrs {
1264+
for _, ctr := range containers {
12521265
include := true
12531266
for _, filter := range filters {
12541267
include = include && filter(ctr)
@@ -1259,7 +1272,7 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
12591272
}
12601273
}
12611274

1262-
return ctrsFiltered, nil
1275+
return ctrsFiltered
12631276
}
12641277

12651278
// GetAllContainers is a helper function for GetContainers

Diff for: pkg/domain/entities/types/container_ps.go

+4
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,7 @@ func (l ListContainer) USERNS() string {
118118
func (l ListContainer) UTS() string {
119119
return l.Namespaces.UTS
120120
}
121+
122+
func (l ListContainer) Commands() []string {
123+
return l.Command
124+
}

Diff for: pkg/domain/filters/containers.go

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/containers/common/pkg/util"
1515
"github.com/containers/podman/v5/libpod"
1616
"github.com/containers/podman/v5/libpod/define"
17+
"github.com/containers/podman/v5/pkg/domain/entities/types"
1718
)
1819

1920
// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
@@ -282,6 +283,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
282283
}
283284
return false
284285
}, filterValueError
286+
case "command":
287+
return func(c *libpod.Container) bool {
288+
return util.StringMatchRegexSlice(c.Command()[0], filterValues)
289+
}, nil
285290
}
286291
return nil, fmt.Errorf("%s is an invalid filter", filter)
287292
}
@@ -315,3 +320,13 @@ func prepareUntilFilterFunc(filterValues []string) (func(container *libpod.Conta
315320
return false
316321
}, nil
317322
}
323+
324+
// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
325+
func GenerateExternalContainerFilterFuncs(filter string, filterValues []string, r *libpod.Runtime) (func(listContainer *types.ListContainer) bool, error) {
326+
if filter == "command" {
327+
return func(listContainer *types.ListContainer) bool {
328+
return util.StringMatchRegexSlice(listContainer.Commands()[0], filterValues)
329+
}, nil
330+
}
331+
return nil, fmt.Errorf("%s is an invalid filter", filter)
332+
}

Diff for: pkg/ps/ps.go

+35-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
2929
pss = []entities.ListContainer{}
3030
)
3131
filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters))
32+
filterExtFuncs := make([]libpod.ExternalContainerFilter, 0, len(options.Filters))
3233
all := options.All || options.Last > 0
3334
if len(options.Filters) > 0 {
3435
for k, v := range options.Filters {
@@ -37,6 +38,14 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
3738
return nil, err
3839
}
3940
filterFuncs = append(filterFuncs, generatedFunc)
41+
42+
if options.External {
43+
generatedExtFunc, err := filters.GenerateExternalContainerFilterFuncs(k, v, runtime)
44+
if err != nil {
45+
return nil, err
46+
}
47+
filterExtFuncs = append(filterExtFuncs, generatedExtFunc)
48+
}
4049
}
4150
}
4251

@@ -87,7 +96,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
8796
}
8897

8998
if options.External {
90-
listCon, err := GetExternalContainerLists(runtime)
99+
listCon, err := GetExternalContainerLists(runtime, filterExtFuncs...)
91100
if err != nil {
92101
return nil, err
93102
}
@@ -107,9 +116,9 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
107116
}
108117

109118
// GetExternalContainerLists returns list of external containers for e.g. created by buildah
110-
func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContainer, error) {
119+
func GetExternalContainerLists(runtime *libpod.Runtime, filterExtFuncs ...libpod.ExternalContainerFilter) ([]entities.ListContainer, error) {
111120
var (
112-
pss = []entities.ListContainer{}
121+
pss = []*entities.ListContainer{}
113122
)
114123

115124
externCons, err := runtime.StorageContainers()
@@ -128,10 +137,31 @@ func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContaine
128137
case err != nil:
129138
return nil, err
130139
default:
131-
pss = append(pss, listCon)
140+
pss = append(pss, &listCon)
132141
}
133142
}
134-
return pss, nil
143+
144+
filteredPss := applyExternalContainersFilters(pss, filterExtFuncs...)
145+
146+
return filteredPss, nil
147+
}
148+
149+
// Apply container filters on bunch of external container lists
150+
func applyExternalContainersFilters(containersList []*entities.ListContainer, filters ...libpod.ExternalContainerFilter) []entities.ListContainer {
151+
ctrsFiltered := make([]entities.ListContainer, 0, len(containersList))
152+
153+
for _, ctr := range containersList {
154+
include := true
155+
for _, filter := range filters {
156+
include = include && filter(ctr)
157+
}
158+
159+
if include {
160+
ctrsFiltered = append(ctrsFiltered, *ctr)
161+
}
162+
}
163+
164+
return ctrsFiltered
135165
}
136166

137167
// ListContainerBatch is used in ps to reduce performance hits by "batching"

Diff for: test/e2e/ps_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,37 @@ var _ = Describe("Podman ps", func() {
405405
Expect(actual).ToNot(ContainSubstring("NAMES"))
406406
})
407407

408+
// This test checks a ps filtering by container command/entrypoint
409+
// To improve the test reliability a container ID is also checked
410+
It("podman ps filter by container command", func() {
411+
matchedSession := podmanTest.Podman([]string{"run", "-d", "--name", "matched", ALPINE, "top"})
412+
matchedSession.WaitWithDefaultTimeout()
413+
containedID := matchedSession.OutputToString() // save container ID returned by the run command
414+
Expect(containedID).ShouldNot(BeEmpty())
415+
Expect(matchedSession).Should(ExitCleanly())
416+
417+
matchedSession = podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--noheading", "--filter", "command=top"})
418+
matchedSession.WaitWithDefaultTimeout()
419+
Expect(matchedSession).Should(ExitCleanly())
420+
421+
output := matchedSession.OutputToStringArray()
422+
Expect(output).To(HaveLen(1))
423+
Expect(output).Should(ContainElement(ContainSubstring(containedID)))
424+
425+
unmatchedSession := podmanTest.Podman([]string{"run", "-d", "--name", "unmatched", ALPINE, "sh"})
426+
unmatchedSession.WaitWithDefaultTimeout()
427+
containedID = unmatchedSession.OutputToString() // save container ID returned by the run command
428+
Expect(containedID).ShouldNot(BeEmpty())
429+
Expect(unmatchedSession).Should(ExitCleanly())
430+
431+
unmatchedSession = podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--noheading", "--filter", "command=fakecommand"})
432+
unmatchedSession.WaitWithDefaultTimeout()
433+
Expect(unmatchedSession).Should(ExitCleanly())
434+
435+
output = unmatchedSession.OutputToStringArray()
436+
Expect(output).To(BeEmpty())
437+
})
438+
408439
It("podman ps mutually exclusive flags", func() {
409440
session := podmanTest.Podman([]string{"ps", "-aqs"})
410441
session.WaitWithDefaultTimeout()

0 commit comments

Comments
 (0)