From fa3b8a75c4ec571f8cbb2622ea624b42bc5c2472 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 10 Jun 2020 16:26:24 +0200 Subject: {create,run} --replace Add a `--replace` flag to the `container {create,run}` commands. If another container with the same name already exists, it will be replaced and removed. Adding this flag is motivated by #5485 to make running Podman in systemd units (or any other scripts/automation) more robust. In case of a crash, a container may not be removed by a sytemd unit anymore. The `--replace` flag allows for supporting crashes. Signed-off-by: Valentin Rothberg --- cmd/podman/common/create.go | 5 +++++ cmd/podman/common/create_opts.go | 1 + cmd/podman/containers/create.go | 17 +++++++++++++++++ cmd/podman/containers/rm.go | 16 +++++++++++++--- cmd/podman/containers/run.go | 6 ++++++ 5 files changed, 42 insertions(+), 3 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index e79c5c20b..921cd5a71 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -373,6 +373,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "read-only-tmpfs", true, "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", ) + createFlags.BoolVar( + &cf.Replace, + "replace", false, + `If a container with the same name exists, replace it`, + ) createFlags.StringVar( &cf.Restart, "restart", "", diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 98dc6744c..49052704e 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -76,6 +76,7 @@ type ContainerCLIOpts struct { ReadOnly bool ReadOnlyTmpFS bool Restart string + Replace bool Rm bool RootFS bool SecurityOpt []string diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index ed09585ba..6269ec781 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -122,6 +122,12 @@ func create(cmd *cobra.Command, args []string) error { return err } + if cliVals.Replace { + if err := replaceContainer(cliVals.Name); err != nil { + return err + } + } + report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s) if err != nil { return err @@ -138,6 +144,17 @@ func create(cmd *cobra.Command, args []string) error { return nil } +func replaceContainer(name string) error { + if len(name) == 0 { + return errors.New("cannot replace container without --name being set") + } + rmOptions := entities.RmOptions{ + Force: true, // force stop & removal + Ignore: true, // ignore errors when a container doesn't exit + } + return removeContainers([]string{name}, rmOptions, false) +} + func createInit(c *cobra.Command) error { if c.Flag("privileged").Changed && c.Flag("security-opt").Changed { logrus.Warn("setting security options with --privileged has no effect") diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index b25473a8d..22d6d59b4 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -87,6 +87,14 @@ func init() { } func rm(cmd *cobra.Command, args []string) error { + return removeContainers(args, rmOptions, true) +} + +// removeContainers will remove the specified containers (names or IDs). +// Allows for sharing removal logic across commands. If setExit is set, +// removeContainers will set the exit code according to the `podman-rm` man +// page. +func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit bool) error { var ( errs utils.OutputErrors ) @@ -96,9 +104,9 @@ func rm(cmd *cobra.Command, args []string) error { return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile") } } - responses, err := registry.ContainerEngine().ContainerRm(context.Background(), args, rmOptions) + responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions) if err != nil { - if len(args) < 2 { + if setExit && len(namesOrIDs) < 2 { setExitCode(err) } return err @@ -109,7 +117,9 @@ func rm(cmd *cobra.Command, args []string) error { if errors.Cause(err) == define.ErrWillDeadlock { logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") } - setExitCode(r.Err) + if setExit { + setExitCode(r.Err) + } errs = append(errs, r.Err) } else { fmt.Println(r.Id) diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 8a02c63c0..b9c196b64 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -129,6 +129,12 @@ func run(cmd *cobra.Command, args []string) error { } } + if cliVals.Replace { + if err := replaceContainer(cliVals.Name); err != nil { + return err + } + } + // If -i is not set, clear stdin if !cliVals.Interactive { runOpts.InputStream = nil -- cgit v1.2.3-54-g00ecf From fe488b5f11836a021bcef6217aeeea41b1321217 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 15 Jun 2020 13:49:36 +0200 Subject: pod create --replace Add a `--replace` flag to the `pod create` command. If another pod with the same name already exists, it will be replaced and removed. Adding this flag is motivated by #5485 to make running Podman in systemd units (or any other scripts/automation) more robust. In case of a crash, a pod may not be removed by a sytemd unit anymore. The `--replace` flag allows for supporting crashes. Note that the `--replace` flag does not require the `--name` flag to be set, so it can be set unconditionally in `podman generate systemd`. Signed-off-by: Valentin Rothberg --- cmd/podman/pods/create.go | 19 +++++++++++++++++++ cmd/podman/pods/rm.go | 18 ++++++++++++------ completions/bash/podman | 1 + docs/source/markdown/podman-pod-create.1.md | 4 ++++ test/e2e/pod_create_test.go | 15 +++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 51b7a7d52..835a62359 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -39,6 +39,7 @@ var ( createOptions entities.PodCreateOptions labels, labelFile []string podIDFile string + replace bool share string ) @@ -61,6 +62,7 @@ func init() { flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") + flags.BoolVar(&replace, "replace", false, "If a pod with the same exists, replace it") flags.StringVar(&share, "share", specgen.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") flags.SetNormalizeFunc(aliasNetworkFlag) } @@ -147,6 +149,12 @@ func create(cmd *cobra.Command, args []string) error { } } + if replace { + if err := replacePod(createOptions.Name); err != nil { + return err + } + } + response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions) if err != nil { return err @@ -159,3 +167,14 @@ func create(cmd *cobra.Command, args []string) error { fmt.Println(response.Id) return nil } + +func replacePod(name string) error { + if len(name) == 0 { + return errors.New("cannot replace pod without --name being set") + } + rmOptions := entities.PodRmOptions{ + Force: true, // stop and remove pod + Ignore: true, // ignore if pod doesn't exist + } + return removePods([]string{name}, rmOptions, false) +} diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index 8de0bce9e..ec8dae1d1 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -58,24 +58,30 @@ func init() { } func rm(cmd *cobra.Command, args []string) error { - var ( - errs utils.OutputErrors - ) - ids, err := common.ReadPodIDFiles(rmOptions.PodIDFiles) if err != nil { return err } args = append(args, ids...) + return removePods(args, rmOptions.PodRmOptions, true) +} - responses, err := registry.ContainerEngine().PodRm(context.Background(), args, rmOptions.PodRmOptions) +// removePods removes the specified pods (names or IDs). Allows for sharing +// pod-removal logic across commands. +func removePods(namesOrIDs []string, rmOptions entities.PodRmOptions, printIDs bool) error { + var errs utils.OutputErrors + + responses, err := registry.ContainerEngine().PodRm(context.Background(), namesOrIDs, rmOptions) if err != nil { return err } + // in the cli, first we print out all the successful attempts for _, r := range responses { if r.Err == nil { - fmt.Println(r.Id) + if printIDs { + fmt.Println(r.Id) + } } else { errs = append(errs, r.Err) } diff --git a/completions/bash/podman b/completions/bash/podman index 6dbe645fe..5e990ec41 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -3118,6 +3118,7 @@ _podman_pod_create() { --help -h --infra + --replace " _complete_ "$options_with_args" "$boolean_options" } diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index de6b600f0..1401400bb 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -102,6 +102,10 @@ Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPO NOTE: This cannot be modified once the pod is created. +**--replace**=**true**|**false** + +If another pod with the same name already exists, replace and remove it. The default is **false**. + **--share**=*namespace* A comma delimited list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index a7d5783cb..8d07f6290 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -305,4 +305,19 @@ var _ = Describe("Podman pod create", func() { data := check.InspectPodToJSON() Expect(data.ID).To(Equal(string(id))) }) + + It("podman pod create --replace", func() { + // Make sure we error out with --name. + session := podmanTest.Podman([]string{"pod", "create", "--replace", ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + // Create and replace 5 times in a row the "same" pod. + podName := "testCtr" + for i := 0; i < 5; i++ { + session = podmanTest.Podman([]string{"pod", "create", "--replace", "--name", podName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + } + }) }) -- cgit v1.2.3-54-g00ecf