diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3f3856906f..daecfbe5e2 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -1808,7 +1808,18 @@ func AutocompletePsFilters(cmd *cobra.Command, _ []string, toComplete string) ([ "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeNames) }, "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) }, "pod=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(cmd, s, completeDefault) }, - "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "restart-policy=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{ + define.RestartPolicyAlways, + define.RestartPolicyNo, + define.RestartPolicyOnFailure, + define.RestartPolicyUnlessStopped, + }, cobra.ShellCompDirectiveNoFileComp + }, + "should-start-on-boot=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp + }, + "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, "status=": func(_ string) ([]string, cobra.ShellCompDirective) { return containerStatuses, cobra.ShellCompDirectiveNoFileComp }, diff --git a/contrib/systemd/system/podman-restart.service.in b/contrib/systemd/system/podman-restart.service.in index 0f1401836f..d59af6395a 100644 --- a/contrib/systemd/system/podman-restart.service.in +++ b/contrib/systemd/system/podman-restart.service.in @@ -9,8 +9,8 @@ After=network-online.target Type=oneshot RemainAfterExit=true Environment=LOGGING="--log-level=info" -ExecStart=@@PODMAN@@ $LOGGING start --all --filter restart-policy=always -ExecStop=@@PODMAN@@ $LOGGING stop --all --filter restart-policy=always +ExecStart=@@PODMAN@@ $LOGGING start --all --filter should-start-on-boot=true +ExecStop=@@PODMAN@@ $LOGGING stop --all --filter should-start-on-boot=true [Install] WantedBy=default.target diff --git a/docs/source/markdown/options/restart.md b/docs/source/markdown/options/restart.md index 74e4e7814f..99fbcd578d 100644 --- a/docs/source/markdown/options/restart.md +++ b/docs/source/markdown/options/restart.md @@ -13,7 +13,7 @@ Valid _policy_ values are: - `never` : Synonym for **no**; do not restart containers on exit - `on-failure[:max_retries]` : Restart containers when they exit with a non-zero exit code, retrying indefinitely or until the optional *max_retries* count is hit - `always` : Restart containers when they exit, regardless of status, retrying indefinitely -- `unless-stopped` : Identical to **always** +- `unless-stopped` : Restart containers when they exit, unless the container was explicitly stopped by the user. After a system reboot, containers with this policy will be restarted by podman-restart.service only if they were not explicitly stopped by the user before the reboot. This differs from **always**, which restarts containers after a system reboot regardless of whether they were user-stopped Podman provides a systemd unit file, podman-restart.service, which restarts containers after a system reboot. diff --git a/docs/source/markdown/podman-pause.1.md.in b/docs/source/markdown/podman-pause.1.md.in index b12945e68e..3cec850fed 100644 --- a/docs/source/markdown/podman-pause.1.md.in +++ b/docs/source/markdown/podman-pause.1.md.in @@ -33,6 +33,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -42,8 +43,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | @@option latest diff --git a/docs/source/markdown/podman-ps.1.md b/docs/source/markdown/podman-ps.1.md index 6144d3bd37..95d62a78cf 100644 --- a/docs/source/markdown/podman-ps.1.md +++ b/docs/source/markdown/podman-ps.1.md @@ -62,6 +62,8 @@ Valid filters are listed below: | 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 | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | #### **--format**=*format* @@ -288,6 +290,14 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS 5e3694604817 quay.io/centos/centos:latest sleep 300 3 minutes ago Up 3 minutes centos-test ``` +Filter containers that need to be restarted after system reboot. +``` +$ podman ps -a --filter should-start-on-boot=true +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +ff660efda598 docker.io/library/nginx:latest nginx -g daemon o... 3 minutes ago Up 3 minutes 0.0.0.0:8080->80/tcp webserver +5693e934f4c6 docker.io/library/redis:latest redis-server 3 minutes ago Exited (0) 3 minutes ago 6379/tcp cache +``` + Use custom format to show container and pod information. ``` $ podman ps --format "{{.Names}} is in pod {{.PodName}} ({{.Pod}})" diff --git a/docs/source/markdown/podman-restart.1.md.in b/docs/source/markdown/podman-restart.1.md.in index e0d302f911..7f315a0ef5 100644 --- a/docs/source/markdown/podman-restart.1.md.in +++ b/docs/source/markdown/podman-restart.1.md.in @@ -36,6 +36,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -45,8 +46,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | @@option latest diff --git a/docs/source/markdown/podman-rm.1.md.in b/docs/source/markdown/podman-rm.1.md.in index 0989b0e620..209bf9d12b 100644 --- a/docs/source/markdown/podman-rm.1.md.in +++ b/docs/source/markdown/podman-rm.1.md.in @@ -40,6 +40,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -49,8 +50,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | #### **--force**, **-f** diff --git a/docs/source/markdown/podman-start.1.md.in b/docs/source/markdown/podman-start.1.md.in index 7f9a10dff6..522636a933 100644 --- a/docs/source/markdown/podman-start.1.md.in +++ b/docs/source/markdown/podman-start.1.md.in @@ -27,7 +27,7 @@ starting multiple containers. @@option detach-keys -#### **--filter**, **-f** +#### **--filter**, **-f**=*filter* Filter what containers are going to be started from the given arguments. Multiple filters can be given with multiple uses of the --filter flag. @@ -41,6 +41,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -50,8 +51,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | @@option interactive diff --git a/docs/source/markdown/podman-stop.1.md.in b/docs/source/markdown/podman-stop.1.md.in index 3a0d3524d5..86d5dd720d 100644 --- a/docs/source/markdown/podman-stop.1.md.in +++ b/docs/source/markdown/podman-stop.1.md.in @@ -39,6 +39,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -48,8 +49,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | @@option ignore diff --git a/docs/source/markdown/podman-unpause.1.md.in b/docs/source/markdown/podman-unpause.1.md.in index fd3e82cb71..89fbb2df89 100644 --- a/docs/source/markdown/podman-unpause.1.md.in +++ b/docs/source/markdown/podman-unpause.1.md.in @@ -33,6 +33,7 @@ Valid filters are listed below: | id | [ID] Container's ID (CID prefix match by default; accepts regex) | | name | [Name] Container's name (accepts regex) | | label | [Key] or [Key=Value] Label assigned to a container | +| label! | [Key] or [Key=Value] Label NOT assigned to a container | | exited | [Int] Container's exit code | | status | [Status] Container's status: 'created', 'initialized', 'exited', 'paused', 'running', 'unknown' | | ancestor | [ImageName] Image or descendant used to create container | @@ -42,8 +43,10 @@ Valid filters are listed below: | health | [Status] healthy or unhealthy | | pod | [Pod] name or full or partial ID of pod | | network | [Network] name or full ID of network | +| restart-policy | [Policy] Container's restart policy (e.g., 'no', 'on-failure', 'always', 'unless-stopped') | | until | [DateTime] Containers created before the given duration or time. | | command | [Command] the command the container is executing, only argv[0] is taken | +| should-start-on-boot | [Bool] Containers that need to be restarted after system reboot. True for containers with restart policy 'always', or 'unless-stopped' that were not explicitly stopped by the user | @@option latest diff --git a/libpod/container.go b/libpod/container.go index 9e322160aa..6e94bbd4cb 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -189,6 +189,7 @@ type ContainerState struct { BindMounts map[string]string `json:"bindMounts,omitempty"` // StoppedByUser indicates whether the container was stopped by an // explicit call to the Stop() API. + // Warning: This field does persist across system reboots. StoppedByUser bool `json:"stoppedByUser,omitempty"` // RestartPolicyMatch indicates whether the conditions for restart // policy have been met. diff --git a/libpod/container_api.go b/libpod/container_api.go index 12d0e858bd..9779461899 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -1093,6 +1093,28 @@ func (c *Container) ShouldRestart(_ context.Context) bool { return c.shouldRestart() } +// Indicate whether or not the container will should start after a reboot of system +func (c *Container) ShouldStartOnBoot() bool { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false + } + } + + if c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated) { + return false + } + + configuredRestartPolicy := c.RestartPolicy() + isAlways := configuredRestartPolicy == define.RestartPolicyAlways + isUnlessStopped := configuredRestartPolicy == define.RestartPolicyUnlessStopped && !c.state.StoppedByUser + + return isAlways || isUnlessStopped +} + // CopyFromArchive copies the contents from the specified tarStream to path // *inside* the container. func (c *Container) CopyFromArchive(_ context.Context, containerPath string, chown, noOverwriteDirNonDir bool, rename map[string]string, tarStream io.Reader) (func() error, error) { diff --git a/libpod/container_internal.go b/libpod/container_internal.go index ff7c420b03..fe33b5a5af 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -635,7 +635,6 @@ func resetContainerState(state *ContainerState) { state.ExecSessions = make(map[string]*ExecSession) state.LegacyExecSessions = nil state.BindMounts = make(map[string]string) - state.StoppedByUser = false state.RestartPolicyMatch = false state.RestartCount = 0 state.Checkpointed = false diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index f3fd02b568..3135fc881d 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -287,6 +287,18 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo return func(c *libpod.Container) bool { return util.StringMatchRegexSlice(c.Command()[0], filterValues) }, nil + case "should-start-on-boot": + wantRestart := false + var err error + for _, fv := range filterValues { + wantRestart, err = strconv.ParseBool(fv) + if err != nil { + return nil, err + } + } + return func(c *libpod.Container) bool { + return c.ShouldStartOnBoot() == wantRestart + }, nil } return nil, fmt.Errorf("%s is an invalid filter", filter) } diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 0689055276..5a5128a542 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -1051,4 +1051,70 @@ var _ = Describe("Podman ps", func() { Expect(output).To(HaveLen(1)) Expect(output).Should(ContainElement(ContainSubstring("late"))) }) + + It("podman ps filter should-start-on-boot", func() { + commands := [][]string{ + {"create", "--restart", "unless-stopped", "--name", "test-unless-stopped-user-stop", ALPINE, "top"}, + {"create", "--restart", "always", "--name", "test-always-user-stop", ALPINE, "top"}, + {"create", "--restart", "no", "--name", "test-no-restart-user-stop", ALPINE, "top"}, + {"create", "--restart", "on-failure", "--name", "test-onfailure-user-stop", ALPINE, "top"}, + {"create", "--restart", "unless-stopped", "--name", "test-unless-stopped-exit-not-started", ALPINE, "false"}, + {"create", "--restart", "always", "--name", "test-always-exit-not-started", ALPINE, "false"}, + {"create", "--restart", "on-failure", "--name", "test-onfailure-exit-not-started", ALPINE, "false"}, + {"start", "test-unless-stopped-user-stop"}, + {"stop", "test-unless-stopped-user-stop"}, + {"start", "test-always-user-stop"}, + {"stop", "test-always-user-stop"}, + {"start", "test-no-restart-user-stop"}, + {"stop", "test-no-restart-user-stop"}, + {"start", "test-onfailure-user-stop"}, + {"stop", "test-onfailure-user-stop"}, + } + + for _, cmd := range commands { + podmanTest.PodmanExitCleanly(cmd...) + } + + commandsExit := [][]string{ + {"run", "--name", "test-unless-stopped-exit-bad", "--restart", "unless-stopped", ALPINE, "false"}, + {"run", "--name", "test-always-exit-bad", "--restart", "always", ALPINE, "false"}, + {"run", "--name", "test-onfailure-exit-bad", "--restart", "on-failure", ALPINE, "false"}, + } + + for _, cmd := range commandsExit { + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(1)) + } + + session := podmanTest.PodmanExitCleanly("ps", "-a", "--filter", "should-start-on-boot=true", "--format", "{{.Names}}") + output := session.OutputToString() + + Expect(output).To(ContainSubstring("test-unless-stopped-exit-bad")) + Expect(output).To(ContainSubstring("test-always-exit-bad")) + Expect(output).To(ContainSubstring("test-always-user-stop")) + + Expect(output).ToNot(ContainSubstring("test-unless-stopped-exit-not-started")) + Expect(output).ToNot(ContainSubstring("test-always-exit-not-started")) + Expect(output).ToNot(ContainSubstring("test-unless-stopped-user-stop")) + Expect(output).ToNot(ContainSubstring("test-no-restart-user-stop")) + Expect(output).ToNot(ContainSubstring("test-onfailure-user-stop")) + Expect(output).ToNot(ContainSubstring("test-onfailure-exit-not-started")) + Expect(output).ToNot(ContainSubstring("test-onfailure-exit-bad")) + + session = podmanTest.PodmanExitCleanly("ps", "-a", "--filter", "should-start-on-boot=false", "--format", "{{.Names}}") + output = session.OutputToString() + + Expect(output).To(ContainSubstring("test-unless-stopped-user-stop")) + Expect(output).To(ContainSubstring("test-no-restart-user-stop")) + Expect(output).To(ContainSubstring("test-unless-stopped-exit-not-started")) + Expect(output).To(ContainSubstring("test-always-exit-not-started")) + Expect(output).To(ContainSubstring("test-onfailure-user-stop")) + Expect(output).To(ContainSubstring("test-onfailure-exit-not-started")) + Expect(output).To(ContainSubstring("test-onfailure-exit-bad")) + + Expect(output).ToNot(ContainSubstring("test-always-user-stop")) + Expect(output).ToNot(ContainSubstring("test-unless-stopped-exit-bad")) + Expect(output).ToNot(ContainSubstring("test-always-exit-bad")) + }) })