Skip to content

Commit

Permalink
add filter for container command
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
arsenalzp committed Dec 18, 2024
1 parent 7b35f4f commit fe91b75
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 8 deletions.
36 changes: 36 additions & 0 deletions cmd/podman/common/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,41 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s
return suggestions, cobra.ShellCompDirectiveNoFileComp
}

func getCommands(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
lsOpts := entities.ContainerListOptions{}

engine, err := setupContainerEngine(cmd)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}

containers, err := engine.ContainerList(registry.GetContext(), lsOpts)
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}

externalContainers, err := engine.ContainerListExternal(registry.GetContext())
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
containers = append(containers, externalContainers...)

for _, container := range containers {
// taking of the first element of commands list was done intentionally
// to exclude command arguments from suggestions (e.g. exclude arguments "-g daemon"
// from "nginx -g daemon" output)
if strings.HasPrefix(container.Command[0], toComplete) {
suggestions = append(suggestions, container.Command[0])
}
}

return suggestions, cobra.ShellCompDirectiveNoFileComp
}

func fdIsNotDir(f *os.File) bool {
stat, err := f.Stat()
if err != nil {
Expand Down Expand Up @@ -1658,6 +1693,7 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string)
kv := keyValueCompletion{
"ancestor=": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) },
"before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) },
"command=": func(s string) ([]string, cobra.ShellCompDirective) { return getCommands(cmd, s) },
"exited=": nil,
"health=": func(_ string) ([]string, cobra.ShellCompDirective) {
return []string{define.HealthCheckHealthy,
Expand Down
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-ps.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Valid filters are listed below:
| pod | [Pod] name or full or partial ID of pod |
| network | [Network] name or full ID of network |
| until | [DateTime] container created before the given duration or time. |
| command | [Command] the command the container is executing, only argv[0] is taken |



#### **--format**=*format*
Expand Down
19 changes: 16 additions & 3 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/libpod/events"
"github.com/containers/podman/v5/libpod/shutdown"
contEntities "github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/domain/entities/reports"
"github.com/containers/podman/v5/pkg/rootless"
"github.com/containers/podman/v5/pkg/specgen"
Expand All @@ -44,6 +45,11 @@ type CtrCreateOption func(*Container) error
// A true return will include the container, a false return will exclude it.
type ContainerFilter func(*Container) bool

// ExternalContainerFilter is a function to determine whether a container list is included
// in command output. Container lists to be outputted are tested using the function.
// A true return will include the container list, a false return will exclude it.
type ExternalContainerFilter func(*contEntities.ListContainer) bool

// NewContainer creates a new container from a given OCI config.
func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, spec *specgen.SpecGenerator, infra bool, options ...CtrCreateOption) (*Container, error) {
if !r.valid {
Expand Down Expand Up @@ -1246,9 +1252,16 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
return nil, err
}

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

return ctrsFiltered, nil
}

// Applies container filters on bunch of containers
func applyContainersFilters(containers []*Container, filters ...ContainerFilter) []*Container {
ctrsFiltered := make([]*Container, 0, len(containers))

for _, ctr := range ctrs {
for _, ctr := range containers {
include := true
for _, filter := range filters {
include = include && filter(ctr)
Expand All @@ -1259,7 +1272,7 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
}
}

return ctrsFiltered, nil
return ctrsFiltered
}

// GetAllContainers is a helper function for GetContainers
Expand Down
4 changes: 4 additions & 0 deletions pkg/domain/entities/types/container_ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ func (l ListContainer) USERNS() string {
func (l ListContainer) UTS() string {
return l.Namespaces.UTS
}

func (l ListContainer) Commands() []string {
return l.Command
}
15 changes: 15 additions & 0 deletions pkg/domain/filters/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/containers/common/pkg/util"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/domain/entities/types"
)

// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
Expand Down Expand Up @@ -282,6 +283,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
}
return false
}, filterValueError
case "command":
return func(c *libpod.Container) bool {
return util.StringMatchRegexSlice(c.Command()[0], filterValues)
}, nil
}
return nil, fmt.Errorf("%s is an invalid filter", filter)
}
Expand Down Expand Up @@ -315,3 +320,13 @@ func prepareUntilFilterFunc(filterValues []string) (func(container *libpod.Conta
return false
}, nil
}

// GenerateContainerFilterFuncs return ContainerFilter functions based of filter.
func GenerateExternalContainerFilterFuncs(filter string, filterValues []string, r *libpod.Runtime) (func(listContainer *types.ListContainer) bool, error) {
if filter == "command" {
return func(listContainer *types.ListContainer) bool {
return util.StringMatchRegexSlice(listContainer.Commands()[0], filterValues)
}, nil
}
return nil, fmt.Errorf("%s is an invalid filter", filter)
}
40 changes: 35 additions & 5 deletions pkg/ps/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
pss = []entities.ListContainer{}
)
filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters))
filterExtFuncs := make([]libpod.ExternalContainerFilter, 0, len(options.Filters))
all := options.All || options.Last > 0
if len(options.Filters) > 0 {
for k, v := range options.Filters {
Expand All @@ -37,6 +38,14 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
return nil, err
}
filterFuncs = append(filterFuncs, generatedFunc)

if options.External {
generatedExtFunc, err := filters.GenerateExternalContainerFilterFuncs(k, v, runtime)
if err != nil {
return nil, err
}
filterExtFuncs = append(filterExtFuncs, generatedExtFunc)
}
}
}

Expand Down Expand Up @@ -87,7 +96,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
}

if options.External {
listCon, err := GetExternalContainerLists(runtime)
listCon, err := GetExternalContainerLists(runtime, filterExtFuncs...)
if err != nil {
return nil, err
}
Expand All @@ -107,9 +116,9 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
}

// GetExternalContainerLists returns list of external containers for e.g. created by buildah
func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContainer, error) {
func GetExternalContainerLists(runtime *libpod.Runtime, filterExtFuncs ...libpod.ExternalContainerFilter) ([]entities.ListContainer, error) {
var (
pss = []entities.ListContainer{}
pss = []*entities.ListContainer{}
)

externCons, err := runtime.StorageContainers()
Expand All @@ -128,10 +137,31 @@ func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContaine
case err != nil:
return nil, err
default:
pss = append(pss, listCon)
pss = append(pss, &listCon)
}
}
return pss, nil

filteredPss := applyExternalContainersFilters(pss, filterExtFuncs...)

return filteredPss, nil
}

// Apply container filters on bunch of external container lists
func applyExternalContainersFilters(containersList []*entities.ListContainer, filters ...libpod.ExternalContainerFilter) []entities.ListContainer {
ctrsFiltered := make([]entities.ListContainer, 0, len(containersList))

for _, ctr := range containersList {
include := true
for _, filter := range filters {
include = include && filter(ctr)
}

if include {
ctrsFiltered = append(ctrsFiltered, *ctr)
}
}

return ctrsFiltered
}

// ListContainerBatch is used in ps to reduce performance hits by "batching"
Expand Down
31 changes: 31 additions & 0 deletions test/e2e/ps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,37 @@ var _ = Describe("Podman ps", func() {
Expect(actual).ToNot(ContainSubstring("NAMES"))
})

// This test checks a ps filtering by container command/entrypoint
// To improve the test reliability a container ID is also checked
It("podman ps filter by container command", func() {
matchedSession := podmanTest.Podman([]string{"run", "-d", "--name", "matched", ALPINE, "top"})
matchedSession.WaitWithDefaultTimeout()
containedID := matchedSession.OutputToString() // save container ID returned by the run command
Expect(containedID).ShouldNot(BeEmpty())
Expect(matchedSession).Should(ExitCleanly())

matchedSession = podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--noheading", "--filter", "command=top"})
matchedSession.WaitWithDefaultTimeout()
Expect(matchedSession).Should(ExitCleanly())

output := matchedSession.OutputToStringArray()
Expect(output).To(HaveLen(1))
Expect(output).Should(ContainElement(ContainSubstring(containedID)))

unmatchedSession := podmanTest.Podman([]string{"run", "-d", "--name", "unmatched", ALPINE, "sh"})
unmatchedSession.WaitWithDefaultTimeout()
containedID = unmatchedSession.OutputToString() // save container ID returned by the run command
Expect(containedID).ShouldNot(BeEmpty())
Expect(unmatchedSession).Should(ExitCleanly())

unmatchedSession = podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--noheading", "--filter", "command=fakecommand"})
unmatchedSession.WaitWithDefaultTimeout()
Expect(unmatchedSession).Should(ExitCleanly())

output = unmatchedSession.OutputToStringArray()
Expect(output).To(BeEmpty())
})

It("podman ps mutually exclusive flags", func() {
session := podmanTest.Podman([]string{"ps", "-aqs"})
session.WaitWithDefaultTimeout()
Expand Down

0 comments on commit fe91b75

Please sign in to comment.