Podman stop --filter flag

Filter flag is added for podman stop and podman --remote stop. Filtering logic is implemented in
getContainersAndInputByContext(). Start filtering can be manipulated to use this logic as well to limit redundancy.

Signed-off-by: Karthik Elango <kelango@redhat.com>
This commit is contained in:
Karthik Elango
2022-06-28 15:31:20 -04:00
committed by Matthew Heon
parent a78be890ee
commit 6d84a9952f
9 changed files with 125 additions and 19 deletions

View File

@ -49,7 +49,9 @@ var (
) )
var ( var (
stopOptions = entities.StopOptions{} stopOptions = entities.StopOptions{
Filters: make(map[string][]string),
}
stopTimeout uint stopTimeout uint
) )
@ -67,6 +69,10 @@ func stopFlags(cmd *cobra.Command) {
flags.UintVarP(&stopTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") flags.UintVarP(&stopTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
_ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) _ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)
filterFlagName := "filter"
flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters)
if registry.IsRemote() { if registry.IsRemote() {
_ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("ignore") _ = flags.MarkHidden("ignore")
@ -97,7 +103,6 @@ func stop(cmd *cobra.Command, args []string) error {
if cmd.Flag("time").Changed { if cmd.Flag("time").Changed {
stopOptions.Timeout = &stopTimeout stopOptions.Timeout = &stopTimeout
} }
for _, cidFile := range cidFiles { for _, cidFile := range cidFiles {
content, err := ioutil.ReadFile(cidFile) content, err := ioutil.ReadFile(cidFile)
if err != nil { if err != nil {
@ -107,6 +112,14 @@ func stop(cmd *cobra.Command, args []string) error {
args = append(args, id) args = append(args, id)
} }
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) < 2 {
return fmt.Errorf("invalid filter %q", f)
}
stopOptions.Filters[split[0]] = append(stopOptions.Filters[split[0]], split[1])
}
responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions) responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions)
if err != nil { if err != nil {
return err return err

View File

@ -86,6 +86,13 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool,
specifiedIDFile = true specifiedIDFile = true
} }
if c.Flags().Changed("filter") {
if argLen > 0 {
return errors.New("--filter takes no arguments")
}
return nil
}
if specifiedIDFile && (specifiedAll || specifiedLatest) { if specifiedIDFile && (specifiedAll || specifiedLatest) {
return fmt.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag) return fmt.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag)
} else if specifiedAll && specifiedLatest { } else if specifiedAll && specifiedLatest {

View File

@ -25,6 +25,30 @@ Stop all running containers. This does not include paused containers.
Read container ID from the specified file and remove the container. Can be specified multiple times. Read container ID from the specified file and remove the container. Can be specified multiple times.
#### **--filter**, **-f**=*filter*
Filter what containers are going to be stopped.
Multiple filters can be given with multiple uses of the --filter flag.
Filters with the same key work inclusive with the only exception being
`label` which is exclusive. Filters with different keys always work exclusive.
Valid filters are listed below:
| **Filter** | **Description** |
| --------------- | -------------------------------------------------------------------------------- |
| id | [ID] Container's ID (accepts regex) |
| name | [Name] Container's name (accepts regex) |
| label | [Key] or [Key=Value] Label assigned to a container |
| exited | [Int] Container's exit code |
| status | [Status] Container's status: 'created', 'exited', 'paused', 'running', 'unknown' |
| ancestor | [ImageName] Image or descendant used to create container |
| before | [ID] or [Name] Containers created before this container |
| since | [ID] or [Name] Containers created since this container |
| volume | [VolumeName] or [MountpointDestination] Volume mounted in container |
| health | [Status] healthy or unhealthy |
| pod | [Pod] name or full or partial ID of pod |
| network | [Network] name or full ID of network |
#### **--ignore**, **-i** #### **--ignore**, **-i**
Ignore errors when specified containers are not in the container store. A user Ignore errors when specified containers are not in the container store. A user

View File

@ -33,9 +33,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return return
} }
name := utils.GetName(r) name := utils.GetName(r)
options := entities.StopOptions{ options := entities.StopOptions{
Ignore: query.Ignore, Ignore: query.Ignore,
} }

View File

@ -80,6 +80,7 @@ type PauseUnpauseReport struct {
} }
type StopOptions struct { type StopOptions struct {
Filters map[string][]string
All bool All bool
Ignore bool Ignore bool
Latest bool Latest bool

View File

@ -37,12 +37,29 @@ import (
) )
// getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids // getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids
// is specified. It also returns a list of the corresponding input name used to look up each container. // is specified. It also returns a list of the corresponding input name used to lookup each container.
func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { func getContainersAndInputByContext(all, latest bool, names []string, filters map[string][]string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) {
var ctr *libpod.Container var ctr *libpod.Container
ctrs = []*libpod.Container{} ctrs = []*libpod.Container{}
filterFuncs := make([]libpod.ContainerFilter, 0, len(filters))
switch { switch {
case len(filters) > 0:
for k, v := range filters {
generatedFunc, err := dfilters.GenerateContainerFilterFuncs(k, v, runtime)
if err != nil {
return nil, nil, err
}
filterFuncs = append(filterFuncs, generatedFunc)
}
ctrs, err = runtime.GetContainers(filterFuncs...)
if err != nil {
return nil, nil, err
}
rawInput = []string{}
for _, candidate := range ctrs {
rawInput = append(rawInput, candidate.ID())
}
case all: case all:
ctrs, err = runtime.GetAllContainers() ctrs, err = runtime.GetAllContainers()
case latest: case latest:
@ -66,13 +83,13 @@ func getContainersAndInputByContext(all, latest bool, names []string, runtime *l
} }
} }
} }
return return ctrs, rawInput, err
} }
// getContainersByContext gets containers whether all, latest, or a slice of names/ids // getContainersByContext gets containers whether all, latest, or a slice of names/ids
// is specified. // is specified.
func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) {
ctrs, _, err = getContainersAndInputByContext(all, latest, names, runtime) ctrs, _, err = getContainersAndInputByContext(all, latest, names, nil, runtime)
return return
} }
@ -150,7 +167,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st
} }
func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) { func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) {
names := namesOrIds names := namesOrIds
ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, names, ic.Libpod) ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, names, options.Filters, ic.Libpod)
if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) {
return nil, err return nil, err
} }
@ -228,7 +245,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin
if err != nil { if err != nil {
return nil, err return nil, err
} }
ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, ic.Libpod) ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, nil, ic.Libpod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -874,7 +891,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
} }
} }
} }
ctrs, rawInputs, err := getContainersAndInputByContext(all, options.Latest, containersNamesOrIds, ic.Libpod) ctrs, rawInputs, err := getContainersAndInputByContext(all, options.Latest, containersNamesOrIds, options.Filters, ic.Libpod)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -91,8 +91,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st
} }
func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, opts entities.StopOptions) ([]*entities.StopReport, error) { func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, opts entities.StopOptions) ([]*entities.StopReport, error) {
reports := []*entities.StopReport{} ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds, opts.Filters)
ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -104,6 +103,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
if to := opts.Timeout; to != nil { if to := opts.Timeout; to != nil {
options.WithTimeout(*to) options.WithTimeout(*to)
} }
reports := []*entities.StopReport{}
for _, c := range ctrs { for _, c := range ctrs {
report := entities.StopReport{ report := entities.StopReport{
Id: c.ID, Id: c.ID,
@ -134,7 +134,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
} }
func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, opts entities.KillOptions) ([]*entities.KillReport, error) { func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, opts entities.KillOptions) ([]*entities.KillReport, error) {
ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds) ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -15,25 +15,29 @@ import (
// FIXME: the `ignore` parameter is very likely wrong here as it should rather // FIXME: the `ignore` parameter is very likely wrong here as it should rather
// be used on *errors* from operations such as remove. // be used on *errors* from operations such as remove.
func getContainersByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, error) { func getContainersByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, error) {
ctrs, _, err := getContainersAndInputByContext(contextWithConnection, all, ignore, namesOrIDs) ctrs, _, err := getContainersAndInputByContext(contextWithConnection, all, ignore, namesOrIDs, nil)
return ctrs, err return ctrs, err
} }
func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, []string, error) { func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string, filters map[string][]string) ([]entities.ListContainer, []string, error) {
if all && len(namesOrIDs) > 0 { if all && len(namesOrIDs) > 0 {
return nil, nil, errors.New("cannot look up containers and all") return nil, nil, errors.New("cannot look up containers and all")
} }
options := new(containers.ListOptions).WithAll(true).WithSync(true) options := new(containers.ListOptions).WithAll(true).WithSync(true).WithFilters(filters)
allContainers, err := containers.List(contextWithConnection, options) allContainers, err := containers.List(contextWithConnection, options)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
rawInputs := []string{} rawInputs := []string{}
if all { switch {
case len(filters) > 0:
for i := range allContainers {
namesOrIDs = append(namesOrIDs, allContainers[i].ID)
}
case all:
for i := range allContainers { for i := range allContainers {
rawInputs = append(rawInputs, allContainers[i].ID) rawInputs = append(rawInputs, allContainers[i].ID)
} }
return allContainers, rawInputs, err return allContainers, rawInputs, err
} }

View File

@ -1,6 +1,7 @@
package integration package integration
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
@ -363,4 +364,45 @@ var _ = Describe("Podman stop", func() {
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
}) })
It("podman stop --filter", func() {
session1 := podmanTest.Podman([]string{"container", "create", ALPINE})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
cid1 := session1.OutputToString()
session1 = podmanTest.Podman([]string{"container", "create", ALPINE})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
cid2 := session1.OutputToString()
session1 = podmanTest.Podman([]string{"container", "create", ALPINE})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
cid3 := session1.OutputToString()
shortCid3 := cid3[0:5]
session1 = podmanTest.Podman([]string{"start", "--all"})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
session1 = podmanTest.Podman([]string{"stop", cid1, "-f", "status=running"})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(125))
session1 = podmanTest.Podman([]string{"stop", "-a", "--filter", fmt.Sprintf("id=%swrongid", shortCid3)})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
Expect(session1.OutputToString()).To(HaveLen(0))
session1 = podmanTest.Podman([]string{"stop", "-a", "--filter", fmt.Sprintf("id=%s", shortCid3)})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
Expect(session1.OutputToString()).To(BeEquivalentTo(cid3))
session1 = podmanTest.Podman([]string{"stop", "-f", fmt.Sprintf("id=%s", cid2)})
session1.WaitWithDefaultTimeout()
Expect(session1).Should(Exit(0))
Expect(session1.OutputToString()).To(BeEquivalentTo(cid2))
})
}) })