mirror of
https://github.com/containers/podman.git
synced 2025-09-26 16:25:00 +08:00

There's a potential race condition where we attempt to attach to a container immediately after it's been stopped, but before the cleanup process has run on it. The existing code doesn't allow an attach to containers in the Stopped state (cleanup process has not run) but does allow an attach to containers in the Exited state (cleanup process has run). This doesn't make very much sense and there's no technical reason to restrict attach to only Exited containers, so allow attaching to Stopped containers. [NO NEW TESTS NEEDED] Testing this is very racy - we need to get in before the cleanup process runs, which isn't really deterministic when we're invoked from a script - like the CI tests. Signed-off-by: Matthew Heon <mheon@redhat.com>
122 lines
3.8 KiB
Go
122 lines
3.8 KiB
Go
package compat
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/containers/podman/v4/libpod"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
|
"github.com/containers/podman/v4/pkg/api/server/idle"
|
|
api "github.com/containers/podman/v4/pkg/api/types"
|
|
"github.com/gorilla/schema"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func AttachContainer(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
|
|
|
query := struct {
|
|
DetachKeys string `schema:"detachKeys"`
|
|
Logs bool `schema:"logs"`
|
|
Stream bool `schema:"stream"`
|
|
Stdin bool `schema:"stdin"`
|
|
Stdout bool `schema:"stdout"`
|
|
Stderr bool `schema:"stderr"`
|
|
}{
|
|
Stream: true,
|
|
}
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
// Detach keys: explicitly set to "" is very different from unset
|
|
// TODO: Our format for parsing these may be different from Docker.
|
|
var detachKeys *string
|
|
if _, found := r.URL.Query()["detachKeys"]; found {
|
|
detachKeys = &query.DetachKeys
|
|
}
|
|
|
|
streams := new(libpod.HTTPAttachStreams)
|
|
streams.Stdout = true
|
|
streams.Stderr = true
|
|
streams.Stdin = true
|
|
useStreams := false
|
|
if _, found := r.URL.Query()["stdin"]; found {
|
|
streams.Stdin = query.Stdin
|
|
useStreams = true
|
|
}
|
|
if _, found := r.URL.Query()["stdout"]; found {
|
|
streams.Stdout = query.Stdout
|
|
useStreams = true
|
|
}
|
|
if _, found := r.URL.Query()["stderr"]; found {
|
|
streams.Stderr = query.Stderr
|
|
useStreams = true
|
|
}
|
|
if !useStreams {
|
|
streams = nil
|
|
}
|
|
if useStreams && !streams.Stdout && !streams.Stderr && !streams.Stdin {
|
|
utils.Error(w, http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true"))
|
|
return
|
|
}
|
|
|
|
// At least one of these must be set
|
|
if !query.Stream && !query.Logs {
|
|
utils.Error(w, http.StatusBadRequest, errors.Errorf("at least one of Logs or Stream must be set"))
|
|
return
|
|
}
|
|
|
|
name := utils.GetName(r)
|
|
ctr, err := runtime.LookupContainer(name)
|
|
if err != nil {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
|
|
state, err := ctr.State()
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
// For Docker compatibility, we need to re-initialize containers in these states.
|
|
if state == define.ContainerStateConfigured || state == define.ContainerStateExited || state == define.ContainerStateStopped {
|
|
if err := ctr.Init(r.Context(), ctr.PodID() != ""); err != nil {
|
|
utils.Error(w, http.StatusConflict, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
|
|
return
|
|
}
|
|
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
|
|
utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers - currently in state %s", state.String()))
|
|
return
|
|
}
|
|
|
|
logErr := func(e error) {
|
|
logrus.Error(errors.Wrapf(e, "error attaching to container %s", ctr.ID()))
|
|
}
|
|
|
|
// Perform HTTP attach.
|
|
// HTTPAttach will handle everything about the connection from here on
|
|
// (including closing it and writing errors to it).
|
|
hijackChan := make(chan bool, 1)
|
|
err = ctr.HTTPAttach(r, w, streams, detachKeys, nil, query.Stream, query.Logs, hijackChan)
|
|
|
|
if <-hijackChan {
|
|
// If connection was Hijacked, we have to signal it's being closed
|
|
t := r.Context().Value(api.IdleTrackerKey).(*idle.Tracker)
|
|
defer t.Close()
|
|
|
|
if err != nil {
|
|
// Cannot report error to client as a 500 as the Upgrade set status to 101
|
|
logErr(err)
|
|
}
|
|
} else {
|
|
// If the Hijack failed we are going to assume we can still inform client of failure
|
|
utils.InternalServerError(w, err)
|
|
logErr(err)
|
|
}
|
|
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
|
|
}
|