diff options
-rw-r--r-- | cmd/podman/containers/restart.go | 67 | ||||
-rw-r--r-- | docs/source/markdown/podman-restart.1.md | 38 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 6 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 27 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 11 | ||||
-rw-r--r-- | test/e2e/restart_test.go | 135 |
6 files changed, 241 insertions, 43 deletions
diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 9d704d671..32c1f6c47 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -3,13 +3,14 @@ package containers import ( "context" "fmt" + "io/ioutil" + "strings" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/cmd/podman/validate" - "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -25,7 +26,7 @@ var ( Long: restartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndIDFile(cmd, args, false, "") + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman restart ctrID @@ -47,20 +48,35 @@ var ( ) var ( - restartOptions = entities.RestartOptions{} - restartTimeout uint + restartOpts = entities.RestartOptions{ + Filters: make(map[string][]string), + } + restartCidFiles = []string{} + restartTimeout uint ) func restartFlags(cmd *cobra.Command) { flags := cmd.Flags() - flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers") - flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used") + flags.BoolVarP(&restartOpts.All, "all", "a", false, "Restart all non-running containers") + flags.BoolVar(&restartOpts.Running, "running", false, "Restart only running containers when --all is used") + + cidfileFlagName := "cidfile" + flags.StringArrayVar(&restartCidFiles, cidfileFlagName, nil, "Read the container ID from the file") + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) + + filterFlagName := "filter" + flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters) timeFlagName := "time" flags.UintVarP(&restartTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") _ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) + if registry.IsRemote() { + _ = flags.MarkHidden("cidfile") + } + flags.SetNormalizeFunc(utils.AliasFlags) } @@ -69,39 +85,54 @@ func init() { Command: restartCommand, }) restartFlags(restartCommand) - validate.AddLatestFlag(restartCommand, &restartOptions.Latest) + validate.AddLatestFlag(restartCommand, &restartOpts.Latest) registry.Commands = append(registry.Commands, registry.CliCommand{ Command: containerRestartCommand, Parent: containerCmd, }) restartFlags(containerRestartCommand) - validate.AddLatestFlag(containerRestartCommand, &restartOptions.Latest) + validate.AddLatestFlag(containerRestartCommand, &restartOpts.Latest) } func restart(cmd *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) - if len(args) < 1 && !restartOptions.Latest && !restartOptions.All { - return fmt.Errorf("you must provide at least one container name or ID: %w", define.ErrInvalidArg) + + if cmd.Flag("time").Changed { + restartOpts.Timeout = &restartTimeout } - if len(args) > 0 && restartOptions.Latest { - return fmt.Errorf("--latest and containers cannot be used together: %w", define.ErrInvalidArg) + + for _, cidFile := range restartCidFiles { + content, err := ioutil.ReadFile(cidFile) + if err != nil { + return fmt.Errorf("error reading CIDFile: %w", err) + } + id := strings.Split(string(content), "\n")[0] + args = append(args, id) } - if cmd.Flag("time").Changed { - restartOptions.Timeout = &restartTimeout + for _, f := range filters { + split := strings.SplitN(f, "=", 2) + if len(split) < 2 { + return fmt.Errorf("invalid filter %q", f) + } + restartOpts.Filters[split[0]] = append(restartOpts.Filters[split[0]], split[1]) } - responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOptions) + + responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOpts) if err != nil { return err } for _, r := range responses { - if r.Err == nil { - fmt.Println(r.Id) - } else { + switch { + case r.Err != nil: errs = append(errs, r.Err) + case r.RawInput != "": + fmt.Println(r.RawInput) + default: + fmt.Println(r.Id) } } return errs.PrintErrors() diff --git a/docs/source/markdown/podman-restart.1.md b/docs/source/markdown/podman-restart.1.md index 323087069..b9da413f7 100644 --- a/docs/source/markdown/podman-restart.1.md +++ b/docs/source/markdown/podman-restart.1.md @@ -14,14 +14,46 @@ Containers will be stopped if they are running and then restarted. Stopped containers will not be stopped and will only be started. ## OPTIONS + #### **--all**, **-a** + Restart all containers regardless of their current state. +#### **--cidfile** + +Read container ID from the specified file and restart the container. Can be specified multiple times. + +#### **--filter**, **-f**=*filter* + +Filter what containers restart. +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 | + #### **--latest**, **-l** + Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) #### **--running** + Restart all containers that are already in the *running* state. #### **--time**, **-t**=*seconds* @@ -59,6 +91,12 @@ Restart all containers $ podman restart --all ``` +Restart container using ID specified in a given files. +``` +$ podman restart --cidfile /home/user/cidfile-1 +$ podman restart --cidfile /home/user/cidfile-1 --cidfile ./cidfile-2 +``` + ## SEE ALSO **[podman(1)](podman.1.md)** diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 3ba507750..91ccdc2b2 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -119,6 +119,7 @@ type KillReport struct { } type RestartOptions struct { + Filters map[string][]string All bool Latest bool Running bool @@ -126,8 +127,9 @@ type RestartOptions struct { } type RestartReport struct { - Err error - Id string //nolint:revive,stylecheck + Err error + Id string //nolint:revive,stylecheck + RawInput string } type RmOptions struct { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 5b5bc665e..08d845d70 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -309,31 +309,42 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, options entities.RestartOptions) ([]*entities.RestartReport, error) { var ( - ctrs []*libpod.Container - err error + ctrs []*libpod.Container + err error + rawInputs = []string{} ) if options.Running { ctrs, err = ic.Libpod.GetRunningContainers() + for _, candidate := range ctrs { + rawInputs = append(rawInputs, candidate.ID()) + } + if err != nil { return nil, err } } else { - ctrs, err = getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, rawInputs, err = getContainersAndInputByContext(options.All, options.Latest, namesOrIds, options.Filters, ic.Libpod) if err != nil { return nil, err } } - + idToRawInput := map[string]string{} + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + idToRawInput[ctrs[i].ID()] = rawInputs[i] + } + } reports := make([]*entities.RestartReport, 0, len(ctrs)) - for _, con := range ctrs { - timeout := con.StopTimeout() + for _, c := range ctrs { + timeout := c.StopTimeout() if options.Timeout != nil { timeout = *options.Timeout } reports = append(reports, &entities.RestartReport{ - Id: con.ID(), - Err: con.RestartWithTimeout(ctx, timeout), + Id: c.ID(), + Err: c.RestartWithTimeout(ctx, timeout), + RawInput: idToRawInput[c.ID()], }) } return reports, nil diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index d49f029d5..046509140 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -183,17 +183,22 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st if to := opts.Timeout; to != nil { options.WithTimeout(int(*to)) } - ctrs, err := getContainersByContext(ic.ClientCtx, opts.All, false, namesOrIds) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds, opts.Filters) if err != nil { return nil, err } + idToRawInput := map[string]string{} + for i := range ctrs { + idToRawInput[ctrs[i].ID] = rawInputs[i] + } for _, c := range ctrs { if opts.Running && c.State != define.ContainerStateRunning.String() { continue } reports = append(reports, &entities.RestartReport{ - Id: c.ID, - Err: containers.Restart(ic.ClientCtx, c.ID, options), + Id: c.ID, + Err: containers.Restart(ic.ClientCtx, c.ID, options), + RawInput: idToRawInput[c.ID], }) } return reports, nil diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index b3052623b..dd0070f54 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,6 +1,8 @@ package integration import ( + "fmt" + "io/ioutil" "os" "time" @@ -33,13 +35,13 @@ var _ = Describe("Podman restart", func() { }) - It("Podman restart bogus container", func() { + It("podman restart bogus container", func() { session := podmanTest.Podman([]string{"start", "123"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(125)) }) - It("Podman restart stopped container by name", func() { + It("podman restart stopped container by name", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1"}) @@ -53,7 +55,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) }) - It("Podman restart stopped container by ID", func() { + It("podman restart stopped container by ID", func() { session := podmanTest.Podman([]string{"create", ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -73,7 +75,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) }) - It("Podman restart running container", func() { + It("podman restart running container", func() { _ = podmanTest.RunTopContainer("test1") ok := WaitForContainer(podmanTest) Expect(ok).To(BeTrue()) @@ -88,7 +90,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) }) - It("Podman container restart running container", func() { + It("podman container restart running container", func() { _ = podmanTest.RunTopContainer("test1") ok := WaitForContainer(podmanTest) Expect(ok).To(BeTrue()) @@ -103,7 +105,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) }) - It("Podman restart multiple containers", func() { + It("podman restart multiple containers", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) @@ -121,7 +123,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) }) - It("Podman restart the latest container", func() { + It("podman restart the latest container", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) @@ -144,7 +146,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) }) - It("Podman restart non-stop container with short timeout", func() { + It("podman restart non-stop container with short timeout", func() { session := podmanTest.Podman([]string{"run", "-d", "--name", "test1", "--env", "STOPSIGNAL=SIGKILL", ALPINE, "sleep", "999"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -157,7 +159,7 @@ var _ = Describe("Podman restart", func() { Expect(timeSince).To(BeNumerically(">", 2*time.Second)) }) - It("Podman restart --all", func() { + It("podman restart --all", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) @@ -177,7 +179,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) }) - It("Podman restart --all --running", func() { + It("podman restart --all --running", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) @@ -197,7 +199,7 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) }) - It("Podman restart a container in a pod and hosts should not duplicated", func() { + It("podman restart a container in a pod and hosts should not duplicated", func() { // Fixes: https://github.com/containers/podman/issues/8921 _, ec, _ := podmanTest.CreatePod(map[string][]string{"--name": {"foobar99"}}) @@ -226,7 +228,7 @@ var _ = Describe("Podman restart", func() { Expect(beforeRestart.OutputToString()).To(Equal(afterRestart.OutputToString())) }) - It("podman restart --all", func() { + It("podman restart all stoped containers with --all", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -247,4 +249,113 @@ var _ = Describe("Podman restart", func() { Expect(session).Should(Exit(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) }) + + It("podman restart --cidfile", func() { + tmpDir, err := ioutil.TempDir("", "") + Expect(err).To(BeNil()) + tmpFile := tmpDir + "cid" + + defer os.RemoveAll(tmpDir) + + session := podmanTest.Podman([]string{"create", "--cidfile", tmpFile, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + cid := session.OutputToStringArray()[0] + + session = podmanTest.Podman([]string{"start", cid}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + result := podmanTest.Podman([]string{"restart", "--cidfile", tmpFile}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + output := result.OutputToString() + Expect(output).To(ContainSubstring(cid)) + }) + + It("podman restart multiple --cidfile", func() { + tmpDir, err := ioutil.TempDir("", "") + Expect(err).To(BeNil()) + tmpFile1 := tmpDir + "cid-1" + tmpFile2 := tmpDir + "cid-2" + + defer os.RemoveAll(tmpDir) + + session := podmanTest.Podman([]string{"run", "--cidfile", tmpFile1, "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + cid1 := session.OutputToStringArray()[0] + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + session = podmanTest.Podman([]string{"run", "--cidfile", tmpFile2, "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + cid2 := session.OutputToStringArray()[0] + Expect(podmanTest.NumberOfContainers()).To(Equal(2)) + + result := podmanTest.Podman([]string{"restart", "--cidfile", tmpFile1, "--cidfile", tmpFile2}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + output := result.OutputToString() + Expect(output).To(ContainSubstring(cid1)) + Expect(output).To(ContainSubstring(cid2)) + Expect(podmanTest.NumberOfContainers()).To(Equal(2)) + }) + + It("podman restart invalid --latest and --cidfile and --all", func() { + SkipIfRemote("--latest flag n/a") + result := podmanTest.Podman([]string{"restart", "--cidfile", "foobar", "--latest"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("cannot be used together")) + result = podmanTest.Podman([]string{"restart", "--cidfile", "foobar", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("cannot be used together")) + result = podmanTest.Podman([]string{"restart", "--cidfile", "foobar", "--all", "--latest"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("cannot be used together")) + result = podmanTest.Podman([]string{"restart", "--latest", "--all"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("cannot be used together")) + }) + + It("podman pause --filter", func() { + session1 := podmanTest.RunTopContainer("") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid1 := session1.OutputToString() + + session1 = podmanTest.RunTopContainer("") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid2 := session1.OutputToString() + + session1 = podmanTest.RunTopContainer("") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid3 := session1.OutputToString() + shortCid3 := cid3[0:5] + + session1 = podmanTest.Podman([]string{"restart", cid1, "-f", "status=test"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(125)) + + session1 = podmanTest.Podman([]string{"restart", "-a", "--filter", fmt.Sprintf("id=%swrongid", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(HaveLen(0)) + + session1 = podmanTest.Podman([]string{"restart", "-a", "--filter", fmt.Sprintf("id=%s", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid3)) + + session1 = podmanTest.Podman([]string{"restart", "-f", fmt.Sprintf("id=%s", cid2)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid2)) + }) }) |