Skip to content

Commit e299759

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]> move ExternalContainerFilter type to entities package Signed-off-by: Oleksandr Krutko <[email protected]> add external filters Signed-off-by: Oleksandr Krutko <[email protected]> add tests for external containers Signed-off-by: Oleksandr Krutko <[email protected]> add test for ps external id, ancestor Signed-off-by: Oleksandr Krutko <[email protected]> add tests for ps external filters of since, before Signed-off-by: Oleksandr Krutko <[email protected]>
1 parent 7b35f4f commit e299759

File tree

8 files changed

+439
-9
lines changed

8 files changed

+439
-9
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

+13-3
Original file line numberDiff line numberDiff line change
@@ -1246,11 +1246,21 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
12461246
return nil, err
12471247
}
12481248

1249-
ctrsFiltered := make([]*Container, 0, len(ctrs))
1249+
ctrsFiltered := applyContainersFilters(ctrs, filters...)
12501250

1251-
for _, ctr := range ctrs {
1251+
return ctrsFiltered, nil
1252+
}
1253+
1254+
// Applies container filters on bunch of containers
1255+
func applyContainersFilters(containers []*Container, filters ...ContainerFilter) []*Container {
1256+
ctrsFiltered := make([]*Container, 0, len(containers))
1257+
1258+
for _, ctr := range containers {
12521259
include := true
12531260
for _, filter := range filters {
1261+
if filter == nil {
1262+
continue
1263+
}
12541264
include = include && filter(ctr)
12551265
}
12561266

@@ -1259,7 +1269,7 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
12591269
}
12601270
}
12611271

1262-
return ctrsFiltered, nil
1272+
return ctrsFiltered
12631273
}
12641274

12651275
// GetAllContainers is a helper function for GetContainers

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

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88
"github.com/containers/podman/v5/pkg/domain/entities/types"
99
)
1010

11+
// ExternalContainerFilter is a function to determine whether a container list is included
12+
// in command output. Container lists to be outputted are tested using the function.
13+
// A true return will include the container list, a false return will exclude it.
14+
type ExternalContainerFilter func(*ListContainer) bool
15+
1116
// ListContainer describes a container suitable for listing
1217
type ListContainer = types.ListContainer
1318

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

+28
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,31 @@ 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+
}
125+
126+
func (l ListContainer) Id() string {
127+
return l.ID
128+
}
129+
130+
func (l ListContainer) LabelsList() map[string]string {
131+
return l.Labels
132+
}
133+
134+
func (l ListContainer) NamesList() []string {
135+
return l.Names
136+
}
137+
138+
func (l ListContainer) ImageInfo() (string, string) {
139+
return l.ImageID, l.Image
140+
}
141+
142+
func (l ListContainer) CreatedTime() time.Time {
143+
return l.Created
144+
}
145+
146+
func (l ListContainer) StatusInfo() string {
147+
return l.Status
148+
}

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

+115
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ 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"
18+
"github.com/containers/storage"
1719
)
1820

1921
// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
@@ -282,6 +284,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
282284
}
283285
return false
284286
}, filterValueError
287+
case "command":
288+
return func(c *libpod.Container) bool {
289+
return util.StringMatchRegexSlice(c.Command()[0], filterValues)
290+
}, nil
285291
}
286292
return nil, fmt.Errorf("%s is an invalid filter", filter)
287293
}
@@ -315,3 +321,112 @@ func prepareUntilFilterFunc(filterValues []string) (func(container *libpod.Conta
315321
return false
316322
}, nil
317323
}
324+
325+
// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
326+
func GenerateExternalContainerFilterFuncs(filter string, filterValues []string, r *libpod.Runtime) (func(listContainer *types.ListContainer) bool, error) {
327+
switch filter {
328+
case "id":
329+
return func(listContainer *types.ListContainer) bool {
330+
return filters.FilterID(listContainer.Id(), filterValues)
331+
}, nil
332+
case "name":
333+
// we only have to match one name
334+
return func(listContainer *types.ListContainer) bool {
335+
namesList := listContainer.NamesList()
336+
337+
for _, f := range filterValues {
338+
f = strings.ReplaceAll(f, "/", "")
339+
if util.StringMatchRegexSlice(f, namesList) {
340+
return true
341+
}
342+
}
343+
344+
return false
345+
}, nil
346+
case "command":
347+
return func(listContainer *types.ListContainer) bool {
348+
return util.StringMatchRegexSlice(listContainer.Commands()[0], filterValues)
349+
}, nil
350+
case "ancestor":
351+
// This needs to refine to match docker
352+
// - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant.
353+
return func(listContainer *types.ListContainer) bool {
354+
for _, filterValue := range filterValues {
355+
rootfsImageID, rootfsImageName := listContainer.ImageInfo()
356+
var imageTag string
357+
var imageNameWithoutTag string
358+
// Compare with ImageID, ImageName
359+
// Will match ImageName if running image has tag latest for other tags exact complete filter must be given
360+
name, tag, hasColon := strings.Cut(rootfsImageName, ":")
361+
if hasColon {
362+
imageNameWithoutTag = name
363+
imageTag = tag
364+
}
365+
366+
if (rootfsImageID == filterValue) ||
367+
util.StringMatchRegexSlice(rootfsImageName, filterValues) ||
368+
(util.StringMatchRegexSlice(imageNameWithoutTag, filterValues) && imageTag == "latest") {
369+
return true
370+
}
371+
}
372+
return false
373+
}, nil
374+
case "before":
375+
var createTime time.Time
376+
var externCons []storage.Container
377+
externCons, err := r.StorageContainers()
378+
if err != nil {
379+
return nil, err
380+
}
381+
382+
for _, filterValue := range filterValues {
383+
for _, ctr := range externCons {
384+
if slices.Contains(ctr.Names, filterValue) {
385+
if createTime.IsZero() || createTime.After(ctr.Created) {
386+
createTime = ctr.Created
387+
}
388+
}
389+
}
390+
}
391+
392+
return func(listContainer *types.ListContainer) bool {
393+
return createTime.After(listContainer.CreatedTime())
394+
}, nil
395+
case "since":
396+
var createTime time.Time
397+
var externCons []storage.Container
398+
externCons, err := r.StorageContainers()
399+
if err != nil {
400+
return nil, err
401+
}
402+
403+
for _, filterValue := range filterValues {
404+
for _, ctr := range externCons {
405+
if slices.Contains(ctr.Names, filterValue) {
406+
if createTime.IsZero() || createTime.After(ctr.Created) {
407+
createTime = ctr.Created
408+
}
409+
}
410+
}
411+
}
412+
413+
return func(listContainer *types.ListContainer) bool {
414+
return createTime.Before(listContainer.CreatedTime())
415+
}, nil
416+
case "until":
417+
until, err := filters.ComputeUntilTimestamp(filterValues)
418+
if err != nil {
419+
return nil, err
420+
}
421+
return func(listContainer *types.ListContainer) bool {
422+
if !until.IsZero() && listContainer.CreatedTime().Before(until) {
423+
return true
424+
}
425+
return false
426+
}, nil
427+
case "restart-policy", "network", "pod", "volume", "health", "label", "exited", "status":
428+
return nil, fmt.Errorf("filter %s is not applicable for external containers", filter)
429+
}
430+
431+
return nil, fmt.Errorf("%s is an invalid filter", filter)
432+
}

Diff for: pkg/ps/ps.go

+41-6
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,33 @@ import (
2424
"github.com/sirupsen/logrus"
2525
)
2626

27+
// ExternalContainerFilter is a function to determine whether a container list is included
28+
// in command output. Container lists to be outputted are tested using the function.
29+
// A true return will include the container list, a false return will exclude it.
30+
type ExternalContainerFilter func(*entities.ListContainer) bool
31+
2732
func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOptions) ([]entities.ListContainer, error) {
2833
var (
2934
pss = []entities.ListContainer{}
3035
)
3136
filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters))
37+
filterExtFuncs := make([]entities.ExternalContainerFilter, 0, len(options.Filters))
3238
all := options.All || options.Last > 0
3339
if len(options.Filters) > 0 {
3440
for k, v := range options.Filters {
3541
generatedFunc, err := filters.GenerateContainerFilterFuncs(k, v, runtime)
36-
if err != nil {
42+
if err != nil && !options.External {
3743
return nil, err
3844
}
3945
filterFuncs = append(filterFuncs, generatedFunc)
46+
47+
if options.External {
48+
generatedExtFunc, err := filters.GenerateExternalContainerFilterFuncs(k, v, runtime)
49+
if err != nil {
50+
return nil, err
51+
}
52+
filterExtFuncs = append(filterExtFuncs, generatedExtFunc)
53+
}
4054
}
4155
}
4256

@@ -87,7 +101,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
87101
}
88102

89103
if options.External {
90-
listCon, err := GetExternalContainerLists(runtime)
104+
listCon, err := GetExternalContainerLists(runtime, filterExtFuncs...)
91105
if err != nil {
92106
return nil, err
93107
}
@@ -107,9 +121,9 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
107121
}
108122

109123
// GetExternalContainerLists returns list of external containers for e.g. created by buildah
110-
func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContainer, error) {
124+
func GetExternalContainerLists(runtime *libpod.Runtime, filterExtFuncs ...entities.ExternalContainerFilter) ([]entities.ListContainer, error) {
111125
var (
112-
pss = []entities.ListContainer{}
126+
pss = []*entities.ListContainer{}
113127
)
114128

115129
externCons, err := runtime.StorageContainers()
@@ -128,10 +142,31 @@ func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContaine
128142
case err != nil:
129143
return nil, err
130144
default:
131-
pss = append(pss, listCon)
145+
pss = append(pss, &listCon)
132146
}
133147
}
134-
return pss, nil
148+
149+
filteredPss := applyExternalContainersFilters(pss, filterExtFuncs...)
150+
151+
return filteredPss, nil
152+
}
153+
154+
// Apply container filters on bunch of external container lists
155+
func applyExternalContainersFilters(containersList []*entities.ListContainer, filters ...entities.ExternalContainerFilter) []entities.ListContainer {
156+
ctrsFiltered := make([]entities.ListContainer, 0, len(containersList))
157+
158+
for _, ctr := range containersList {
159+
include := true
160+
for _, filter := range filters {
161+
include = include && filter(ctr)
162+
}
163+
164+
if include {
165+
ctrsFiltered = append(ctrsFiltered, *ctr)
166+
}
167+
}
168+
169+
return ctrsFiltered
135170
}
136171

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

0 commit comments

Comments
 (0)