diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index c7eda521d5..849221e816 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -50,6 +50,7 @@ var ( envInput, envFile []string execOpts entities.ExecOptions execDetach bool + execCidFile string ) func execFlags(cmd *cobra.Command) { @@ -63,6 +64,10 @@ func execFlags(cmd *cobra.Command) { flags.StringVar(&execOpts.DetachKeys, detachKeysFlagName, containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl- where is one of: a-z, @, ^, [, , or _") _ = cmd.RegisterFlagCompletionFunc(detachKeysFlagName, common.AutocompleteDetachKeys) + cidfileFlagName := "cidfile" + flags.StringVar(&execCidFile, cidfileFlagName, "", "File to read the container ID from") + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) + envFlagName := "env" flags.StringArrayVarP(&envInput, envFlagName, "e", []string{}, "Set environment variables") _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) @@ -116,16 +121,12 @@ func init() { } func exec(cmd *cobra.Command, args []string) error { - var nameOrID string + nameOrID, command, err := determineTargetCtrAndCmd(args, execOpts.Latest, execCidFile != "") + if err != nil { + return err + } + execOpts.Cmd = command - if len(args) == 0 && !execOpts.Latest { - return errors.New("exec requires the name or ID of a container or the --latest flag") - } - execOpts.Cmd = args - if !execOpts.Latest { - execOpts.Cmd = args[1:] - nameOrID = strings.TrimPrefix(args[0], "/") - } // Validate given environment variables execOpts.Envs = make(map[string]string) for _, f := range envFile { @@ -191,6 +192,33 @@ func exec(cmd *cobra.Command, args []string) error { return nil } +// determineTargetCtrAndCmd determines which command exec should run in which container +func determineTargetCtrAndCmd(args []string, latestSpecified bool, execCidFileProvided bool) (string, []string, error) { + var nameOrID string + var command []string + + if len(args) == 0 && !latestSpecified && !execCidFileProvided { + return "", nil, errors.New("exec requires the name or ID of a container or the --latest or --cidfile flag") + } else if latestSpecified && execCidFileProvided { + return "", nil, errors.New("--latest and --cidfile can not be used together") + } + command = args + if !latestSpecified { + if !execCidFileProvided { + // assume first arg to be name or ID + command = args[1:] + nameOrID = strings.TrimPrefix(args[0], "/") + } else { + content, err := os.ReadFile(execCidFile) + if err != nil { + return "", nil, fmt.Errorf("reading CIDFile: %w", err) + } + nameOrID = strings.Split(string(content), "\n")[0] + } + } + return nameOrID, command, nil +} + func execWait(ctr string, seconds int32) error { maxDuration := time.Duration(seconds) * time.Second interval := 100 * time.Millisecond diff --git a/docs/source/markdown/podman-exec.1.md.in b/docs/source/markdown/podman-exec.1.md.in index c12d86a2ad..35013a9d0b 100644 --- a/docs/source/markdown/podman-exec.1.md.in +++ b/docs/source/markdown/podman-exec.1.md.in @@ -13,6 +13,10 @@ podman\-exec - Execute a command in a running container ## OPTIONS +#### **--cidfile**=*file* + +Read the ID of the target container from the specified *file*. + #### **--detach**, **-d** Start the exec session, but do not attach to it. The command runs in the background, and the exec session is automatically removed when it completes. The **podman exec** command prints the ID of the exec session and exits immediately after it starts. diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 6c3acc4864..7850eadb84 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -60,6 +60,38 @@ var _ = Describe("Podman exec", func() { Expect(session).Should(ExitCleanly()) }) + It("podman exec simple command using cidfile", func() { + cidFile := filepath.Join(tempdir, "cid") + session := podmanTest.RunTopContainerWithArgs("test1", []string{"--cidfile", cidFile}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + result := podmanTest.Podman([]string{"exec", "--cidfile", cidFile, "ls"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(ExitCleanly()) + }) + + It("podman exec latest and cidfile", func() { + SkipIfRemote("--latest flag n/a") + + cidFile := filepath.Join(tempdir, "cid") + session := podmanTest.RunTopContainerWithArgs("test1", []string{"--cidfile", cidFile}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + result := podmanTest.Podman([]string{"exec", "--cidfile", cidFile, "--latest", "ls"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(ExitWithError(125, `--latest and --cidfile can not be used together`)) + }) + + It("podman exec nonextant cidfile", func() { + session := podmanTest.Podman([]string{"exec", "--cidfile", "foobar", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, `reading CIDFile: open foobar: no such file or directory`)) + }) + It("podman exec environment test", func() { setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout()