From fa3b8a75c4ec571f8cbb2622ea624b42bc5c2472 Mon Sep 17 00:00:00 2001
From: Valentin Rothberg <rothberg@redhat.com>
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 <rothberg@redhat.com>
---
 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 ++++++
 docs/source/markdown/podman-create.1.md |  4 ++++
 docs/source/markdown/podman-run.1.md    |  4 ++++
 test/e2e/create_test.go                 | 15 +++++++++++++++
 test/e2e/run_test.go                    | 15 +++++++++++++++
 9 files changed, 80 insertions(+), 3 deletions(-)

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
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index dbc835920..1da9d72e6 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -662,6 +662,10 @@ its root filesystem mounted as read only prohibiting any writes.
 
 If container is running in --read-only mode, then mount a read-write tmpfs on /run, /tmp, and /var/tmp.  The default is *true*
 
+**--replace**=**true**|**false**
+
+If another container with the same name already exists, replace and remove it.  The default is **false**.
+
 **--restart**=*policy*
 
 Restart policy to follow when containers exit.
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 22f7cae09..3e1ade047 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -671,6 +671,10 @@ its root filesystem mounted as read only prohibiting any writes.
 
 If container is running in **--read-only** mode, then mount a read-write tmpfs on _/run_, _/tmp_, and _/var/tmp_. The default is **true**.
 
+**--replace**=**true**|**false**
+
+If another container with the same name already exists, replace and remove it.  The default is **false**.
+
 **--restart**=*policy*
 
 Restart policy to follow when containers exit.
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index b9a1ff83d..822e470f2 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -429,4 +429,19 @@ var _ = Describe("Podman create", func() {
 		Expect(len(data)).To(Equal(1))
 		Expect(data[0].HostConfig.NanoCpus).To(Equal(int64(nanoCPUs)))
 	})
+
+	It("podman create --replace", func() {
+		// Make sure we error out with --name.
+		session := podmanTest.Podman([]string{"create", "--replace", ALPINE, "/bin/sh"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(125))
+
+		// Create and replace 5 times in a row the "same" container.
+		ctrName := "testCtr"
+		for i := 0; i < 5; i++ {
+			session = podmanTest.Podman([]string{"create", "--replace", "--name", ctrName, ALPINE, "/bin/sh"})
+			session.WaitWithDefaultTimeout()
+			Expect(session.ExitCode()).To(Equal(0))
+		}
+	})
 })
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 59215c7e5..76944b3db 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -931,4 +931,19 @@ USER mail`
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 	})
+
+	It("podman run --replace", func() {
+		// Make sure we error out with --name.
+		session := podmanTest.Podman([]string{"create", "--replace", ALPINE, "/bin/sh"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(125))
+
+		// Run and replace 5 times in a row the "same" container.
+		ctrName := "testCtr"
+		for i := 0; i < 5; i++ {
+			session := podmanTest.Podman([]string{"run", "--detach", "--replace", "--name", ctrName, ALPINE, "/bin/sh"})
+			session.WaitWithDefaultTimeout()
+			Expect(session.ExitCode()).To(Equal(0))
+		}
+	})
 })
-- 
cgit v1.2.3-54-g00ecf