From 7487854f10374865d74cedfa3260efdfedde0135 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 11 May 2026 15:07:47 -0400 Subject: [PATCH] Allow running exec with no attach streams requested This is a small Docker compat fix with the API exec endpoints. With Docker, sending a bare Exec Create (command only, nothing else set) and then an Exec Start without Detach set performs a detached exec. With Podman, we threw an error that at least one stream must be attached to. Fix is trivial; look up the session before start, check the config for attach streams, and force detach on if none are set. Signed-off-by: Matthew Heon --- pkg/api/handlers/compat/exec.go | 13 +++++++++++++ pkg/api/server/register_exec.go | 3 ++- test/apiv2/20-containers.at | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 25fdaa973f..24a918d00a 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -175,6 +175,19 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } + // If no streams were requested for attach, detach is implied + if !bodyParams.Detach { + execSession, err := sessionCtr.ExecSession(sessionID) + if err != nil { + utils.InternalServerError(w, err) + return + } + + if !execSession.Config.AttachStderr && !execSession.Config.AttachStdout && !execSession.Config.AttachStdin { + bodyParams.Detach = true + } + } + if bodyParams.Detach { // If we are detaching, we do NOT want to hijack. // Instead, we perform a detached start, and return 200 if diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index 17d97c2890..188884019e 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -273,7 +273,8 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // properties: // Detach: // type: boolean - // description: Detach from the command. + // description: | + // "Detach from the command. Detach defaults to true if no attach streams were specified when the session was created (IE AttachStdin, AttachStdout, AttachStderr are all false)." // Tty: // type: boolean // description: Allocate a pseudo-TTY. diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index d64e8bcacc..40f8ef3019 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -988,6 +988,29 @@ t POST libpod/containers/$cid/update \ t DELETE containers/$cid 204 +# Verify that exec session with no attach streams specified can be started +CTRNAME=exectest +podman run -d --name $CTRNAME $IMAGE top +echo '{ "Cmd":["sh", "-c", "echo hello > /tmp/testfile"]}' >${TMPD}/exec_empty.json +t POST "containers/$CTRNAME/exec" ${TMPD}/exec_empty.json 201 .Id~[0-9a-f]\\{64\\} +eid=$(jq -r '.Id' <<<"$output") +t POST exec/$eid/start 200 +# Need to wait for the exec to actually run, given it's detached. +# Use $PODMAN_BIN --root directly instead of the podman wrapper, since we +# need to capture the command output. +TRIES=5 +while [[ $TRIES -gt 0 ]]; do + exec_out=$($PODMAN_BIN --root $WORKDIR/server_root exec $CTRNAME ls /tmp/testfile 2>/dev/null) + if [[ "$exec_out" = "/tmp/testfile" ]]; then + break + fi + sleep 1 + TRIES=$((TRIES - 1)) +done +out=$($PODMAN_BIN --root $WORKDIR/server_root exec $CTRNAME cat /tmp/testfile 2>/dev/null) +is "$out" "hello" +podman rm -f $CTRNAME + rm -rf $TMPD podman container rm -fa