Add restart --cidfile, --filter

--cidfile : Read container ID from the specified file and restart the container.
--filter : restart the filtered container.

Signed-off-by: Toshiki Sonoda <sonoda.toshiki@fujitsu.com>
This commit is contained in:
Toshiki Sonoda 2022-08-12 09:22:53 +09:00
parent 0fc27ce980
commit 0dbbb1cb3f
6 changed files with 242 additions and 44 deletions

View File

@ -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 len(args) > 0 && restartOptions.Latest {
return fmt.Errorf("--latest and containers cannot be used together: %w", define.ErrInvalidArg)
}
if cmd.Flag("time").Changed {
restartOptions.Timeout = &restartTimeout
restartOpts.Timeout = &restartTimeout
}
responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOptions)
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)
}
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, 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()

View File

@ -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)**

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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))
})
})