Merge pull request #6553 from vrothberg/replace

--replace for containers and pods
This commit is contained in:
OpenShift Merge Robot 2020-06-15 12:54:36 -04:00 committed by GitHub
commit 10c6c806ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 252 additions and 28 deletions

View file

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

View file

@ -76,6 +76,7 @@ type ContainerCLIOpts struct {
ReadOnly bool
ReadOnlyTmpFS bool
Restart string
Replace bool
Rm bool
RootFS bool
SecurityOpt []string

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3118,6 +3118,7 @@ _podman_pod_create() {
--help
-h
--infra
--replace
"
_complete_ "$options_with_args" "$boolean_options"
}

View file

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

View file

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

View file

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

View file

@ -207,24 +207,42 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
info.CreateCommand = filterPodFlags(info.CreateCommand)
}
// Enforce detaching
//
// since we use systemd `Type=forking` service
// @see https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
// when we generated systemd service file with the --new param,
// `ExecStart` will have `/usr/bin/podman run ...`
// if `info.CreateCommand` has no `-d` or `--detach` param,
// podman will run the container in default attached mode,
// as a result, `systemd start` will wait the `podman run` command exit until failed with timeout error.
// Presence check for certain flags/options.
hasDetachParam := false
hasNameParam := false
hasReplaceParam := false
for _, p := range info.CreateCommand[index:] {
if p == "--detach" || p == "-d" {
switch p {
case "--detach", "-d":
hasDetachParam = true
case "--name":
hasNameParam = true
case "--replace":
hasReplaceParam = true
}
}
if !hasDetachParam {
// Enforce detaching
//
// since we use systemd `Type=forking` service @see
// https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
// when we generated systemd service file with the
// --new param, `ExecStart` will have `/usr/bin/podman
// run ...` if `info.CreateCommand` has no `-d` or
// `--detach` param, podman will run the container in
// default attached mode, as a result, `systemd start`
// will wait the `podman run` command exit until failed
// with timeout error.
startCommand = append(startCommand, "-d")
}
if hasNameParam && !hasReplaceParam {
// Enforce --replace for named containers. This will
// make systemd units more robuts as it allows them to
// start after system crashes (see
// github.com/containers/libpod/issues/5485).
startCommand = append(startCommand, "--replace")
}
startCommand = append(startCommand, info.CreateCommand[index:]...)
info.ExecStartPre = "/usr/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}"

View file

@ -103,7 +103,7 @@ Type=forking
[Install]
WantedBy=multi-user.target default.target`
goodNameNew := `# jadda-jadda.service
goodWithNameAndGeneric := `# jadda-jadda.service
# autogenerated by Podman CI
[Unit]
@ -116,7 +116,30 @@ After=network-online.target
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
PIDFile=%t/jadda-jadda.pid
KillMode=none
Type=forking
[Install]
WantedBy=multi-user.target default.target`
goodWithExplicitShortDetachParam := `# jadda-jadda.service
# autogenerated by Podman CI
[Unit]
Description=Podman jadda-jadda.service
Documentation=man:podman-generate-systemd(1)
Wants=network.target
After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
PIDFile=%t/jadda-jadda.pid
@ -139,7 +162,7 @@ After=network-online.target
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file /tmp/pod-foobar.pod-id-file -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file /tmp/pod-foobar.pod-id-file --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
PIDFile=%t/jadda-jadda.pid
@ -148,6 +171,7 @@ Type=forking
[Install]
WantedBy=multi-user.target default.target`
goodNameNewDetach := `# jadda-jadda.service
# autogenerated by Podman CI
@ -161,7 +185,7 @@ After=network-online.target
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
PIDFile=%t/jadda-jadda.pid
@ -274,7 +298,7 @@ WantedBy=multi-user.target default.target`
CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
EnvVariable: EnvVariable,
},
goodNameNew,
goodWithNameAndGeneric,
true,
false,
},
@ -290,7 +314,7 @@ WantedBy=multi-user.target default.target`
CreateCommand: []string{"I'll get stripped", "container", "run", "-d", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"},
EnvVariable: EnvVariable,
},
goodNameNew,
goodWithExplicitShortDetachParam,
true,
false,
},

View file

@ -265,7 +265,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
if podCreateIndex == 0 {
return "", errors.Errorf("pod does not appear to be created via `podman pod create`: %v", info.CreateCommand)
}
podRootArgs = info.CreateCommand[1 : podCreateIndex-2]
podRootArgs = info.CreateCommand[0 : podCreateIndex-2]
podCreateArgs = filterPodFlags(info.CreateCommand[podCreateIndex+1:])
}
// We're hard-coding the first five arguments and append the
@ -277,6 +277,21 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
"--infra-conmon-pidfile", "{{.PIDFile}}",
"--pod-id-file", "{{.PodIDFile}}"}...)
// Presence check for certain flags/options.
hasNameParam := false
hasReplaceParam := false
for _, p := range podCreateArgs {
switch p {
case "--name":
hasNameParam = true
case "--replace":
hasReplaceParam = true
}
}
if hasNameParam && !hasReplaceParam {
podCreateArgs = append(podCreateArgs, "--replace")
}
startCommand = append(startCommand, podCreateArgs...)
info.ExecStartPre1 = "/usr/bin/rm -f {{.PIDFile}} {{.PodIDFile}}"

View file

@ -36,7 +36,7 @@ func TestValidateRestartPolicyPod(t *testing.T) {
}
func TestCreatePodSystemdUnit(t *testing.T) {
podGoodName := `# pod-123abc.service
podGood := `# pod-123abc.service
# autogenerated by Podman CI
[Unit]
@ -56,6 +56,32 @@ PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635
KillMode=none
Type=forking
[Install]
WantedBy=multi-user.target default.target`
podGoodNamedNew := `# pod-123abc.service
# autogenerated by Podman CI
[Unit]
Description=Podman pod-123abc.service
Documentation=man:podman-generate-systemd(1)
Wants=network.target
After=network-online.target
Requires=container-1.service container-2.service
Before=container-1.service container-2.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
ExecStartPre=/usr/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id
ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace
ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10
ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id
PIDFile=%t/pod-123abc.pid
KillMode=none
Type=forking
[Install]
WantedBy=multi-user.target default.target`
@ -63,6 +89,7 @@ WantedBy=multi-user.target default.target`
name string
info podInfo
want string
new bool
wantErr bool
}{
{"pod",
@ -76,7 +103,24 @@ WantedBy=multi-user.target default.target`
PodmanVersion: "CI",
RequiredServices: []string{"container-1", "container-2"},
},
podGoodName,
podGood,
false,
false,
},
{"pod --new",
podInfo{
Executable: "/usr/bin/podman",
ServiceName: "pod-123abc",
InfraNameOrID: "jadda-jadda-infra",
RestartPolicy: "on-failure",
PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
StopTimeout: 10,
PodmanVersion: "CI",
RequiredServices: []string{"container-1", "container-2"},
CreateCommand: []string{"podman", "pod", "create", "--name", "foo"},
},
podGoodNamedNew,
true,
false,
},
}
@ -86,6 +130,7 @@ WantedBy=multi-user.target default.target`
t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{
Files: false,
New: test.new,
}
got, err := executePodTemplate(&test.info, opts)
if (err != nil) != test.wantErr {

View file

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

View file

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

View file

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