Files
podman/pkg/api/handlers/compat/containers_top.go
Matt Heon e04668c8ca Match output of Compat Top API to Docker
We were only splitting on tabs, not spaces, so we returned just a
single line most of the time, not an array of the fields in the
output of `ps`. Unfortunately, some of these fields are allowed
to contain spaces themselves, which makes things complicated, but
we got lucky in that Docker took the simplest possible solution
and just assumed that only one field would contain spaces and it
would always be the last one, which is easy enough to duplicate
on our end.

Fixes #23981

Signed-off-by: Matt Heon <mheon@redhat.com>
2024-09-17 11:34:22 -04:00

132 lines
3.3 KiB
Go

//go:build !remote
package compat
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/pkg/api/handlers"
"github.com/containers/podman/v5/pkg/api/handlers/utils"
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/sirupsen/logrus"
)
func TopContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := utils.GetDecoder(r)
var psArgs []string
if !utils.IsLibpodRequest(r) {
psArgs = []string{"-ef"}
}
query := struct {
Delay int `schema:"delay"`
PsArgs []string `schema:"ps_args"`
Stream bool `schema:"stream"`
}{
Delay: 5,
PsArgs: psArgs,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}
if query.Delay < 1 {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("\"delay\" parameter of value %d < 1", query.Delay))
return
}
name := utils.GetName(r)
c, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
statusWritten := false
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
args := query.PsArgs
if len(args) == 1 &&
utils.IsLibpodRequest(r) {
if _, err := utils.SupportedVersion(r, "< 4.8.0"); err == nil {
// Ugly workaround for older clients which used to send arguments comma separated.
args = strings.Split(args[0], ",")
}
}
loop: // break out of for/select infinite` loop
for {
select {
case <-r.Context().Done():
break loop
default:
output, err := c.Top(args)
if err != nil {
if !statusWritten {
utils.InternalServerError(w, err)
} else {
logrus.Errorf("From %s %q : %v", r.Method, r.URL, err)
}
break loop
}
if len(output) > 0 {
body := handlers.ContainerTopOKBody{}
body.Titles = utils.PSTitles(output[0])
for i := range body.Titles {
body.Titles[i] = strings.TrimSpace(body.Titles[i])
}
for _, line := range output[1:] {
process := strings.FieldsFunc(line, func(r rune) bool {
return r == ' ' || r == '\t'
})
if len(process) > len(body.Titles) {
// Docker assumes the last entry is *always* command
// Which can include spaces.
// All other descriptors are assumed to NOT include extra spaces.
// So combine any extras.
cmd := strings.Join(process[len(body.Titles)-1:], " ")
var finalProc []string
finalProc = append(finalProc, process[:len(body.Titles)-1]...)
finalProc = append(finalProc, cmd)
body.Processes = append(body.Processes, finalProc)
} else {
body.Processes = append(body.Processes, process)
}
}
if err := encoder.Encode(body); err != nil {
if !statusWritten {
utils.InternalServerError(w, err)
} else {
logrus.Errorf("From %s %q : %v", r.Method, r.URL, err)
}
break loop
}
// after the first write we can no longer send a different status code
statusWritten = true
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
if query.Stream {
time.Sleep(time.Duration(query.Delay) * time.Second)
} else {
break loop
}
}
}
}