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 <alexander.krutko@gmail.com>

add a test, improve logic of command filter

Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>

improve a test

Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>
  • Loading branch information
arsenalzp committed Dec 7, 2024
1 parent 7b35f4f commit 7a8043c
Show file tree
Hide file tree
Showing 6 changed files with 119 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
37 changes: 34 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 := r.ApplyContainersFilters(ctrs, filters...)

return ctrsFiltered, nil
}

// Apply container filters on bunch of containers
func (r *Runtime) 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,25 @@ func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*
}
}

return ctrsFiltered, nil
return ctrsFiltered
}

// Apply container filters on bunch of external container lists
func (r *Runtime) ApplyExternalContainersFilters(containersList []*contEntities.ListContainer, filters ...ExternalContainerFilter) []contEntities.ListContainer {
ctrsFiltered := make([]contEntities.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
}

// 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)
}
22 changes: 17 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,13 @@ 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 := runtime.ApplyExternalContainersFilters(pss, filterExtFuncs...)

return filteredPss, nil
}

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

It("podman ps filter by container command", func() {
session := podmanTest.Podman([]string{"run", "-d", "--name", "test1", ALPINE, "top"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())

result := podmanTest.Podman([]string{"ps", "-a", "--filter", "command=top"})
result.WaitWithDefaultTimeout()
Expect(result).Should(ExitCleanly())

output := result.OutputToStringArray()
Expect(output).To(HaveLen(2))
})

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

0 comments on commit 7a8043c

Please sign in to comment.