podman {pod,} rm/stop: add --ignore flag

Add an --ignore flag to podman rm and stop. When specified, Podman will
ignore "no such {container,pod}" errors that occur when a specified
container/pod is not present in the store (anymore).  The motivation
behind adding this flag is to write more robust systemd services using
Podman.  A user might have manually decided to remove a container/pod
which would lead to a failure during the `ExecStop` directive of a
systemd service referencing that container/pod.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2019-11-20 14:10:48 +01:00
parent 6187e72435
commit a3d13fb286
17 changed files with 200 additions and 19 deletions

View File

@ -368,6 +368,7 @@ type PodRestartValues struct {
type PodRmValues struct { type PodRmValues struct {
PodmanCommand PodmanCommand
All bool All bool
Ignore bool
Force bool Force bool
Latest bool Latest bool
} }
@ -389,6 +390,7 @@ type PodStatsValues struct {
type PodStopValues struct { type PodStopValues struct {
PodmanCommand PodmanCommand
All bool All bool
Ignore bool
Latest bool Latest bool
Timeout uint Timeout uint
} }
@ -484,6 +486,7 @@ type RmValues struct {
PodmanCommand PodmanCommand
All bool All bool
Force bool Force bool
Ignore bool
Latest bool Latest bool
Storage bool Storage bool
Volumes bool Volumes bool
@ -561,6 +564,7 @@ type StatsValues struct {
type StopValues struct { type StopValues struct {
PodmanCommand PodmanCommand
All bool All bool
Ignore bool
Latest bool Latest bool
Timeout uint Timeout uint
CIDFiles []string CIDFiles []string

View File

@ -41,7 +41,9 @@ func init() {
flags := podRmCommand.Flags() flags := podRmCommand.Flags()
flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods") flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods")
flags.BoolVarP(&podRmCommand.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") flags.BoolVarP(&podRmCommand.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false")
flags.BoolVarP(&podRmCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing")
flags.BoolVarP(&podRmCommand.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") flags.BoolVarP(&podRmCommand.Latest, "latest", "l", false, "Remove the latest pod podman is aware of")
markFlagHiddenForRemoteClient("ignore", flags)
markFlagHiddenForRemoteClient("latest", flags) markFlagHiddenForRemoteClient("latest", flags)
} }

View File

@ -41,8 +41,10 @@ func init() {
podStopCommand.SetUsageTemplate(UsageTemplate()) podStopCommand.SetUsageTemplate(UsageTemplate())
flags := podStopCommand.Flags() flags := podStopCommand.Flags()
flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods") flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods")
flags.BoolVarP(&podStopCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing")
flags.BoolVarP(&podStopCommand.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") flags.BoolVarP(&podStopCommand.Latest, "latest", "l", false, "Stop the latest pod podman is aware of")
flags.UintVarP(&podStopCommand.Timeout, "timeout", "t", 0, "Seconds to wait for pod stop before killing the container") flags.UintVarP(&podStopCommand.Timeout, "timeout", "t", 0, "Seconds to wait for pod stop before killing the container")
markFlagHiddenForRemoteClient("ignore", flags)
markFlagHiddenForRemoteClient("latest", flags) markFlagHiddenForRemoteClient("latest", flags)
} }

View File

@ -40,14 +40,16 @@ func init() {
rmCommand.SetUsageTemplate(UsageTemplate()) rmCommand.SetUsageTemplate(UsageTemplate())
flags := rmCommand.Flags() flags := rmCommand.Flags()
flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library") flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library")
flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container")
flags.StringArrayVarP(&rmCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") flags.StringArrayVarP(&rmCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
markFlagHiddenForRemoteClient("storage", flags) markFlagHiddenForRemoteClient("ignore", flags)
markFlagHiddenForRemoteClient("latest", flags)
markFlagHiddenForRemoteClient("cidfile", flags) markFlagHiddenForRemoteClient("cidfile", flags)
markFlagHiddenForRemoteClient("latest", flags)
markFlagHiddenForRemoteClient("storage", flags)
} }
// rmCmd removes one or more containers // rmCmd removes one or more containers
@ -58,10 +60,10 @@ func rmCmd(c *cliconfig.RmValues) error {
} }
defer runtime.DeferredShutdown(false) defer runtime.DeferredShutdown(false)
// Storage conflicts with --all/--latest/--volumes // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore
if c.Storage { if c.Storage {
if c.All || c.Latest || c.Volumes || c.CIDFiles != nil { if c.All || c.Ignore || c.Latest || c.Volumes || c.CIDFiles != nil {
return errors.Errorf("--storage conflicts with --volumes, --all, --latest and --cidfile") return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile")
} }
} }

View File

@ -39,12 +39,14 @@ func init() {
stopCommand.SetUsageTemplate(UsageTemplate()) stopCommand.SetUsageTemplate(UsageTemplate())
flags := stopCommand.Flags() flags := stopCommand.Flags()
flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers") flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers")
flags.BoolVarP(&stopCommand.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.StringArrayVarP(&stopCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
flags.BoolVarP(&stopCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVarP(&stopCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.UintVar(&stopCommand.Timeout, "time", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") flags.UintVar(&stopCommand.Timeout, "time", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
flags.UintVarP(&stopCommand.Timeout, "timeout", "t", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") flags.UintVarP(&stopCommand.Timeout, "timeout", "t", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
flags.StringArrayVarP(&stopCommand.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
markFlagHiddenForRemoteClient("latest", flags) markFlagHiddenForRemoteClient("latest", flags)
markFlagHiddenForRemoteClient("cidfile", flags) markFlagHiddenForRemoteClient("cidfile", flags)
markFlagHiddenForRemoteClient("ignore", flags)
} }
// stopCmd stops a container or containers // stopCmd stops a container or containers

View File

@ -2155,6 +2155,8 @@ _podman_rm() {
-f -f
--help --help
-h -h
--ignore
-i
--latest --latest
-l -l
--storage --storage
@ -2430,6 +2432,8 @@ _podman_stop() {
--cidfile --cidfile
-h -h
--help --help
--ignore
-i
--latest --latest
-l -l
" "
@ -2990,6 +2994,8 @@ _podman_pod_rm() {
--all --all
--help --help
-h -h
--ignore
-i
-f -f
--force --force
--latest --latest
@ -3040,6 +3046,8 @@ _podman_pod_stop() {
-a -a
--cleanup --cleanup
--help --help
--ignore
-i
-h -h
--latest --latest
-l -l

View File

@ -15,6 +15,12 @@ podman\-pod\-rm - Remove one or more pods
Remove all pods. Can be used in conjunction with \-f as well. Remove all pods. Can be used in conjunction with \-f as well.
**--ignore**, **-i**
Ignore errors when specified pods are not in the container store. A user might
have decided to manually remove a pod which would lead to a failure during the
ExecStop directive of a systemd service referencing that pod.
**--latest**, **-l** **--latest**, **-l**
Instead of providing the pod name or ID, remove the last created pod. Instead of providing the pod name or ID, remove the last created pod.

View File

@ -15,6 +15,12 @@ Stop containers in one or more pods. You may use pod IDs or names as input.
Stops all pods Stops all pods
**--ignore**, **-i**
Ignore errors when specified pods are not in the container store. A user might
have decided to manually remove a pod which would lead to a failure during the
ExecStop directive of a systemd service referencing that pod.
**--latest**, **-l** **--latest**, **-l**
Instead of providing the pod name or ID, stop the last created pod. Instead of providing the pod name or ID, stop the last created pod.

View File

@ -30,6 +30,12 @@ Containers could have been created by a different container engine.
In addition, forcing can be used to remove unusable containers, e.g. containers In addition, forcing can be used to remove unusable containers, e.g. containers
whose OCI runtime has become unavailable. whose OCI runtime has become unavailable.
**--ignore**, **-i**
Ignore errors when specified containers are not in the container store. A user
might have decided to manually remove a container which would lead to a failure
during the ExecStop directive of a systemd service referencing that container.
**--latest**, **-l** **--latest**, **-l**
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman Instead of providing the container name or ID, use the last created container. If you use methods other than Podman

View File

@ -25,6 +25,12 @@ 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.
**--ignore**, **-i**
Ignore errors when specified containers are not in the container store. A user
might have decided to manually remove a container which would lead to a failure
during the ExecStop directive of a systemd service referencing that container.
**--latest**, **-l** **--latest**, **-l**
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman Instead of providing the container name or ID, use the last created container. If you use methods other than Podman

View File

@ -90,7 +90,7 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa
} }
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime) ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
if err != nil { if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
return nil, nil, err return nil, nil, err
} }
@ -224,7 +224,7 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
} }
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime) ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
if err != nil { if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
// Failed to get containers. If force is specified, get the containers ID // Failed to get containers. If force is specified, get the containers ID
// and evict them // and evict them
if !cli.Force { if !cli.Force {
@ -235,6 +235,10 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
logrus.Debugf("Evicting container %q", ctr) logrus.Debugf("Evicting container %q", ctr)
id, err := r.EvictContainer(ctx, ctr, cli.Volumes) id, err := r.EvictContainer(ctx, ctr, cli.Volumes)
if err != nil { if err != nil {
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
continue
}
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id) failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
continue continue
} }
@ -252,6 +256,10 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
Fn: func() error { Fn: func() error {
err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes) err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes)
if err != nil { if err != nil {
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
return nil
}
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error()) logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
} }
return err return err

View File

@ -15,6 +15,7 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/adapter/shortcuts"
ann "github.com/containers/libpod/pkg/annotations" ann "github.com/containers/libpod/pkg/annotations"
@ -94,7 +95,7 @@ func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValue
podids []string podids []string
) )
pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil { if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchPod) {
errs = append(errs, err) errs = append(errs, err)
return nil, errs return nil, errs
} }
@ -151,7 +152,7 @@ func (r *LocalRuntime) StopPods(ctx context.Context, cli *cliconfig.PodStopValue
podids []string podids []string
) )
pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil { if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchPod) {
errs = append(errs, err) errs = append(errs, err)
return nil, errs return nil, errs
} }

View File

@ -2,9 +2,11 @@ package shortcuts
import ( import (
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/sirupsen/logrus"
) )
// GetPodsByContext gets pods whether all, latest, or a slice of names/ids // GetPodsByContext returns a slice of pods. Note that all, latest and pods are
// mutually exclusive arguments.
func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) { func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) {
var outpods []*libpod.Pod var outpods []*libpod.Pod
if all { if all {
@ -18,17 +20,24 @@ func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime)
outpods = append(outpods, p) outpods = append(outpods, p)
return outpods, nil return outpods, nil
} }
var err error
for _, p := range pods { for _, p := range pods {
pod, err := runtime.LookupPod(p) pod, e := runtime.LookupPod(p)
if err != nil { if e != nil {
return nil, err // Log all errors here, so callers don't need to.
logrus.Debugf("Error looking up pod %q: %v", p, e)
if err == nil {
err = e
}
} else {
outpods = append(outpods, pod)
} }
outpods = append(outpods, pod)
} }
return outpods, nil return outpods, err
} }
// GetContainersByContext gets pods whether all, latest, or a slice of names/ids // GetContainersByContext gets pods whether all, latest, or a slice of names/ids
// 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) {
var ctr *libpod.Container var ctr *libpod.Container
ctrs = []*libpod.Container{} ctrs = []*libpod.Container{}
@ -41,10 +50,15 @@ func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Ru
} else { } else {
for _, n := range names { for _, n := range names {
ctr, e := runtime.LookupContainer(n) ctr, e := runtime.LookupContainer(n)
if e != nil && err == nil { if e != nil {
err = e // Log all errors here, so callers don't need to.
logrus.Debugf("Error looking up container %q: %v", n, e)
if err == nil {
err = e
}
} else {
ctrs = append(ctrs, ctr)
} }
ctrs = append(ctrs, ctr)
} }
} }
return return

View File

@ -186,4 +186,47 @@ var _ = Describe("Podman pod rm", func() {
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result.OutputToString()).To(BeEmpty()) Expect(result.OutputToString()).To(BeEmpty())
}) })
It("podman rm bogus pod", func() {
session := podmanTest.Podman([]string{"pod", "rm", "bogus"})
session.WaitWithDefaultTimeout()
// TODO: `podman rm` returns 1 for a bogus container. Should the RC be consistent?
Expect(session.ExitCode()).To(Equal(125))
})
It("podman rm bogus pod and a running pod", func() {
_, ec, podid1 := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
session := podmanTest.RunTopContainerInPod("test1", podid1)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "rm", "bogus", "test1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
session = podmanTest.Podman([]string{"pod", "rm", "test1", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
})
It("podman rm --ignore bogus pod and a running pod", func() {
SkipIfRemote()
_, ec, podid1 := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
session := podmanTest.RunTopContainerInPod("test1", podid1)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "rm", "--force", "--ignore", "bogus", "test1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "rm", "--ignore", "test1", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
}) })

View File

@ -38,6 +38,46 @@ var _ = Describe("Podman pod stop", func() {
Expect(session.ExitCode()).To(Equal(125)) Expect(session.ExitCode()).To(Equal(125))
}) })
It("podman pod stop --ignore bogus pod", func() {
SkipIfRemote()
session := podmanTest.Podman([]string{"pod", "stop", "--ignore", "123"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
It("podman stop bogus pod and a running pod", func() {
_, ec, podid1 := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
session := podmanTest.RunTopContainerInPod("test1", podid1)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "bogus", "test1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
})
It("podman stop --ignore bogus pod and a running pod", func() {
SkipIfRemote()
_, ec, podid1 := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
session := podmanTest.RunTopContainerInPod("test1", podid1)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "--ignore", "bogus", "test1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"pod", "stop", "--ignore", "test1", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
It("podman pod stop single empty pod", func() { It("podman pod stop single empty pod", func() {
_, ec, podid := podmanTest.CreatePod("") _, ec, podid := podmanTest.CreatePod("")
Expect(ec).To(Equal(0)) Expect(ec).To(Equal(0))

View File

@ -232,4 +232,20 @@ var _ = Describe("Podman rm", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125)) Expect(session.ExitCode()).To(Equal(125))
}) })
It("podman rm --ignore bogus container and a running container", func() {
SkipIfRemote()
session := podmanTest.RunTopContainer("test1")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"rm", "--force", "--ignore", "bogus", "test1"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"rm", "--ignore", "test1", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
}) })

View File

@ -40,6 +40,21 @@ var _ = Describe("Podman stop", func() {
Expect(session.ExitCode()).To(Equal(125)) Expect(session.ExitCode()).To(Equal(125))
}) })
It("podman stop --ignore bogus container", func() {
SkipIfRemote()
session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
cid := session.OutputToString()
session = podmanTest.Podman([]string{"stop", "--ignore", "foobar", cid})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
output := session.OutputToString()
Expect(output).To(ContainSubstring(cid))
})
It("podman stop container by id", func() { It("podman stop container by id", func() {
session := podmanTest.RunTopContainer("") session := podmanTest.RunTopContainer("")
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()