Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/podman/containers/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func stopFlags(cmd *cobra.Command) {
flags.IntVarP(&stopTimeout, timeFlagName, "t", int(containerConfig.Engine.StopTimeout), "Seconds to wait for stop before killing the container")
_ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)

signalFlagName := "signal"
flags.StringVarP(&stopOptions.Signal, signalFlagName, "s", "", "Signal to send for the graceful stop, before the timeout (default: the container's stop signal)")
_ = cmd.RegisterFlagCompletionFunc(signalFlagName, common.AutocompleteStopSignal)

filterFlagName := "filter"
flags.StringArrayVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters)
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-stop.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Command does not fail when *file* is missing and user specified --ignore.

@@option latest

#### **--signal**, **-s**=*signal*

Signal to send to the container for the graceful stop, before the timeout. For
more information on Linux signals, refer to *signal(7)*. The default is the
container's stop signal (see **--stop-signal** in **podman-create(1)**).

@@option time

## EXAMPLES
Expand Down
23 changes: 16 additions & 7 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,24 +283,33 @@ func (c *Container) Stop() error {
// manually. If timeout is 0, SIGKILL will be used immediately to kill the
// container.
func (c *Container) StopWithTimeout(timeout uint) (finalErr error) {
return c.StopWithArgs(timeout, true)
return c.StopWithArgs(timeout, 0, true)
}

// StopWithTimeoutAndSignal is like StopWithTimeout but delivers the given signal
// for the graceful stop instead of the container's configured StopSignal. A
// signal of 0 means "use the configured StopSignal" (the Docker stop --signal
// override).
func (c *Container) StopWithTimeoutAndSignal(timeout uint, signal uint) (finalErr error) {
return c.StopWithArgs(timeout, signal, true)
}

// StopService stops the container without marking it as stopped by user (e.g. for
// systemd ExecStop). Containers with restart policy unless-stopped will be
// eligible to start again on next boot.
func (c *Container) StopService(timeout uint) (finalErr error) {
return c.StopWithArgs(timeout, false)
return c.StopWithArgs(timeout, 0, false)
}

// StopWithArgs is a version of Stop that allows a timeout to be specified manually
// and controls whether to set the StoppedByUser state field. If timeout is 0,
// SIGKILL will be used immediately to kill the container.
// StopWithArgs is a version of Stop that allows a timeout and a graceful stop
// signal to be specified manually, and controls whether to set the StoppedByUser
// state field. A signal of 0 uses the container's configured StopSignal. If
// timeout is 0, SIGKILL will be used immediately to kill the container.
//
// An explicit stop is treated as a user-driven lifecycle action. Because of
// that, this path may not trigger automatic restart-policy handling in cleanup,
// even when stoppedByUser is false.
func (c *Container) StopWithArgs(timeout uint, stoppedByUser bool) (finalErr error) {
func (c *Container) StopWithArgs(timeout uint, signal uint, stoppedByUser bool) (finalErr error) {
// Have to lock the pod the container is a part of.
// This prevents running `podman stop` at the same time a
// `podman pod start` is running, which could lead to weird races.
Expand Down Expand Up @@ -338,7 +347,7 @@ func (c *Container) StopWithArgs(timeout uint, stoppedByUser bool) (finalErr err
return err
}
}
return c.stopInternal(timeout, stoppedByUser)
return c.stopInternal(timeout, signal, stoppedByUser)
}

// Kill sends a signal to a container
Expand Down
8 changes: 4 additions & 4 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,12 +1375,12 @@ func (c *Container) stopWithAll() bool {

// Internal, non-locking function to stop container
func (c *Container) stop(timeout uint) error {
return c.stopInternal(timeout, true)
return c.stopInternal(timeout, 0, true)
}

// Internal, non-locking function to stop container
// stoppedByUser controls whether to set the StoppedByUser state field.
func (c *Container) stopInternal(timeout uint, stoppedByUser bool) error {
func (c *Container) stopInternal(timeout uint, signal uint, stoppedByUser bool) error {
// This is explicit container stop that flows pass through Running -> Stopping -> Stopped/Exited states.
// As a result, this does not satisfy the Running/Paused -> Stopped/Exited
// transition that is required to trigger restart policy during cleanup.
Expand Down Expand Up @@ -1433,7 +1433,7 @@ func (c *Container) stopInternal(timeout uint, stoppedByUser bool) error {
c.lock.Unlock()
}

stopErr := c.ociRuntime.StopContainer(c, timeout, all)
stopErr := c.ociRuntime.StopContainer(c, timeout, signal, all)

if !c.batched {
c.lock.Lock()
Expand Down Expand Up @@ -1502,7 +1502,7 @@ func (c *Container) waitForConmonToExitAndSave() error {
// this to get the real exit code... But I'm not
// that dedicated.
all := c.stopWithAll()
if err := c.ociRuntime.StopContainer(c, 0, all); err != nil {
if err := c.ociRuntime.StopContainer(c, 0, 0, all); err != nil {
logrus.Errorf("Error stopping container %s after Conmon exited prematurely: %v", c.ID(), err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion libpod/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type OCIRuntime interface { //nolint:interfacebloat
// If all is set, we will attempt to use the --all flag will `kill` in
// the OCI runtime to kill all processes in the container, including
// exec sessions. This is only supported if the container has cgroups.
StopContainer(ctr *Container, timeout uint, all bool) error
StopContainer(ctr *Container, timeout uint, signal uint, all bool) error
// DeleteContainer deletes the given container from the OCI runtime.
DeleteContainer(ctr *Container) error
// PauseContainer pauses the given container.
Expand Down
7 changes: 5 additions & 2 deletions libpod/oci_conmon_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ func (r *ConmonOCIRuntime) killContainer(ctr *Container, signal uint, all, captu
// after to pull the exit code.
// IMPORTANT: Thus function is called from an unlocked container state in
// the stop() code path so do not modify the state here.
func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) error {
func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, signal uint, all bool) error {
logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID)

// Ping the container to see if it's alive
Expand Down Expand Up @@ -366,7 +366,10 @@ func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool)
}

if timeout > 0 {
stopSignal := ctr.config.StopSignal
stopSignal := signal
if stopSignal == 0 {
stopSignal = ctr.config.StopSignal
}
if stopSignal == 0 {
stopSignal = uint(syscall.SIGTERM)
}
Expand Down
2 changes: 1 addition & 1 deletion libpod/oci_missing.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (r *MissingRuntime) KillContainer(_ *Container, _ uint, _ bool) error {
}

// StopContainer is not available as the runtime is missing
func (r *MissingRuntime) StopContainer(_ *Container, _ uint, _ bool) error {
func (r *MissingRuntime) StopContainer(_ *Container, _ uint, _ uint, _ bool) error {
return r.printError()
}

Expand Down
8 changes: 5 additions & 3 deletions pkg/api/handlers/compat/containers_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {

// /{version}/containers/(name)/stop
query := struct {
Ignore bool `schema:"ignore"`
DockerTimeout int `schema:"t"`
LibpodTimeout uint `schema:"timeout"`
Ignore bool `schema:"ignore"`
DockerTimeout int `schema:"t"`
LibpodTimeout uint `schema:"timeout"`
Signal string `schema:"signal"`
}{
// override any golang type defaults
}
Expand All @@ -38,6 +39,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
name := utils.GetName(r)
options := entities.StopOptions{
Ignore: query.Ignore,
Signal: query.Signal,
}
if utils.IsLibpodRequest(r) {
if _, found := r.URL.Query()["timeout"]; found {
Expand Down
1 change: 1 addition & 0 deletions pkg/bindings/containers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ type WaitOptions struct {
//go:generate go run ../generator/generator.go StopOptions
type StopOptions struct {
Ignore *bool
Signal *string
Timeout *uint
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/bindings/containers/types_stop_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type StopOptions struct {
Ignore bool
Latest bool
Timeout *uint
// Signal optionally overrides the container's configured StopSignal for the
// graceful stop (the Docker `stop --signal` override). Empty means StopSignal.
Signal string
}

type StopReport struct {
Expand Down
12 changes: 11 additions & 1 deletion pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,17 @@ func (ic *ContainerEngine) containerStopImpl(ctx context.Context, namesOrIds []s
}

func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) {
return ic.containerStopImpl(ctx, namesOrIds, options, func(c *libpod.Container, t uint) error { return c.StopWithTimeout(t) })
var sig uint // 0 = use the container's configured StopSignal
if options.Signal != "" {
s, err := signal.ParseSignalNameOrNumber(options.Signal)
if err != nil {
return nil, err
}
sig = uint(s)
}
return ic.containerStopImpl(ctx, namesOrIds, options, func(c *libpod.Container, t uint) error {
return c.StopWithTimeoutAndSignal(t, sig)
})
}

func (ic *ContainerEngine) ContainerStopService(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func (ic *ContainerEngine) ContainerStop(_ context.Context, namesOrIds []string,
idToRawInput[ctrs[i].ID] = rawInputs[i]
}
options := new(containers.StopOptions).WithIgnore(opts.Ignore)
if opts.Signal != "" {
options.WithSignal(opts.Signal)
}
if to := opts.Timeout; to != nil {
options.WithTimeout(*to)
}
Expand Down
18 changes: 18 additions & 0 deletions test/e2e/stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,24 @@ var _ = Describe("Podman stop", func() {
Expect(strings.TrimSpace(finalCtrs.OutputToString())).To(Equal(""))
})

It("podman stop container --signal", func() {
// The container traps SIGUSR1 and exits 42; --signal must deliver that
// instead of the container's default stop signal.
ctr := "test-stop-signal"
session := podmanTest.Podman([]string{"run", "-d", "--name", ctr, ALPINE, "sh", "-c", "trap 'exit 42' USR1; while :; do sleep 0.1; done"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())

stop := podmanTest.Podman([]string{"stop", "--signal", "SIGUSR1", "-t", "20", ctr})
stop.WaitWithDefaultTimeout()
Expect(stop).Should(ExitCleanly())

inspect := podmanTest.Podman([]string{"inspect", ctr, "--format", "{{.State.ExitCode}}"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
Expect(inspect.OutputToString()).To(Equal("42"))
})

It("podman stop container --timeout", func() {
session := podmanTest.Podman([]string{"run", "-d", "--name", "test5", ALPINE, "sleep", "100"})
session.WaitWithDefaultTimeout()
Expand Down
Loading