aboutsummaryrefslogtreecommitdiff
path: root/pkg/api/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r--pkg/api/handlers/compat/containers_attach.go79
-rw-r--r--pkg/api/handlers/compat/containers_create.go39
-rw-r--r--pkg/api/handlers/compat/containers_logs.go76
-rw-r--r--pkg/api/handlers/compat/events.go154
-rw-r--r--pkg/api/handlers/compat/exec.go14
5 files changed, 242 insertions, 120 deletions
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go
index 325f96b40..71586fca4 100644
--- a/pkg/api/handlers/compat/containers_attach.go
+++ b/pkg/api/handlers/compat/containers_attach.go
@@ -1,23 +1,22 @@
package compat
import (
+ "bufio"
"fmt"
+ "io"
+ "net"
"net/http"
+ "strings"
"github.com/containers/libpod/v2/libpod"
"github.com/containers/libpod/v2/libpod/define"
"github.com/containers/libpod/v2/pkg/api/handlers/utils"
+ "github.com/containers/libpod/v2/pkg/api/server/idletracker"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-// AttachHeader is the literal header sent for upgraded/hijacked connections for
-// attach, sourced from Docker at:
-// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
-// Using literally to ensure compatibility with existing clients.
-const AttachHeader = "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n"
-
func AttachContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -98,21 +97,11 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
- // Hijack the connection
- hijacker, ok := w.(http.Hijacker)
- if !ok {
- utils.InternalServerError(w, errors.Errorf("unable to hijack connection"))
- return
- }
-
- connection, buffer, err := hijacker.Hijack()
+ connection, buffer, err := AttachConnection(w, r)
if err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection"))
+ utils.InternalServerError(w, err)
return
}
-
- fmt.Fprintf(connection, AttachHeader)
-
logrus.Debugf("Hijack for attach of container %s successful", ctr.ID())
// Perform HTTP attach.
@@ -126,3 +115,57 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
}
+
+type HijackedConnection struct {
+ net.Conn // Connection
+ idleTracker *idletracker.IdleTracker // Connection tracker
+}
+
+func (c HijackedConnection) Close() error {
+ logrus.Debugf("Hijacked connection closed")
+
+ c.idleTracker.TrackHijackedClosed()
+ return c.Conn.Close()
+}
+
+func AttachConnection(w http.ResponseWriter, r *http.Request) (net.Conn, *bufio.ReadWriter, error) {
+ idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker)
+
+ // Hijack the connection
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ return nil, nil, errors.Errorf("unable to hijack connection")
+ }
+
+ connection, buffer, err := hijacker.Hijack()
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error hijacking connection")
+ }
+ trackedConnection := HijackedConnection{
+ Conn: connection,
+ idleTracker: idleTracker,
+ }
+
+ WriteAttachHeaders(r, trackedConnection)
+
+ return trackedConnection, buffer, nil
+}
+
+func WriteAttachHeaders(r *http.Request, connection io.Writer) {
+ // AttachHeader is the literal header sent for upgraded/hijacked connections for
+ // attach, sourced from Docker at:
+ // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
+ // Using literally to ensure compatibility with existing clients.
+ c := r.Header.Get("Connection")
+ proto := r.Header.Get("Upgrade")
+ if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") {
+ // OK - can't upgrade if not requested or protocol is not specified
+ fmt.Fprintf(connection,
+ "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+ } else {
+ // Upraded
+ fmt.Fprintf(connection,
+ "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n",
+ proto)
+ }
+}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index a4511b3b0..cbcda474a 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -1,6 +1,7 @@
package compat
import (
+ "context"
"encoding/json"
"fmt"
"net/http"
@@ -8,6 +9,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/v2/libpod"
+ "github.com/containers/libpod/v2/libpod/define"
image2 "github.com/containers/libpod/v2/libpod/image"
"github.com/containers/libpod/v2/pkg/api/handlers"
"github.com/containers/libpod/v2/pkg/api/handlers/utils"
@@ -40,9 +42,15 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
}
if len(input.HostConfig.Links) > 0 {
utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter"))
+ return
}
newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image)
if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchImage {
+ utils.Error(w, "No such image", http.StatusNotFound, err)
+ return
+ }
+
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()"))
return
}
@@ -51,7 +59,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()"))
return
}
- cc, err := makeCreateConfig(containerConfig, input, newImage)
+ cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
@@ -60,7 +68,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.CreateContainer(r.Context(), w, runtime, &cc)
}
-func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool
@@ -79,6 +87,22 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta
workDir = input.WorkingDir
}
+ if input.Entrypoint == nil {
+ entrypointSlice, err := newImage.Entrypoint(ctx)
+ if err != nil {
+ return createconfig.CreateConfig{}, err
+ }
+ input.Entrypoint = entrypointSlice
+ }
+
+ if len(input.Cmd) == 0 {
+ cmdSlice, err := newImage.Cmd(ctx)
+ if err != nil {
+ return createconfig.CreateConfig{}, err
+ }
+ input.Cmd = cmdSlice
+ }
+
stopTimeout := containerConfig.Engine.StopTimeout
if input.StopTimeout != nil {
stopTimeout = uint(*input.StopTimeout)
@@ -217,5 +241,16 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta
Pid: pidConfig,
}
+
+ fullCmd := append(input.Entrypoint, input.Cmd...)
+ if len(fullCmd) > 0 {
+ m.PodmanPath = fullCmd[0]
+ if len(fullCmd) == 1 {
+ m.Args = fullCmd
+ } else {
+ m.Args = fullCmd[1:]
+ }
+ }
+
return m, nil
}
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go
index 8147f4d38..30ee030e8 100644
--- a/pkg/api/handlers/compat/containers_logs.go
+++ b/pkg/api/handlers/compat/containers_logs.go
@@ -92,7 +92,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
options.WaitGroup = &wg
logChannel := make(chan *logs.LogLine, tail+1)
- if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ if err := runtime.Log(r.Context(), []*libpod.Container{ctnr}, options, logChannel); err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
return
}
@@ -105,50 +105,48 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var frame strings.Builder
header := make([]byte, 8)
- for ok := true; ok; ok = query.Follow {
- for line := range logChannel {
- if _, found := r.URL.Query()["until"]; found {
- if line.Time.After(until) {
- break
- }
+ for line := range logChannel {
+ if _, found := r.URL.Query()["until"]; found {
+ if line.Time.After(until) {
+ break
}
+ }
- // Reset buffer we're ready to loop again
- frame.Reset()
- switch line.Device {
- case "stdout":
- if !query.Stdout {
- continue
- }
- header[0] = 1
- case "stderr":
- if !query.Stderr {
- continue
- }
- header[0] = 2
- default:
- // Logging and moving on is the best we can do here. We may have already sent
- // a Status and Content-Type to client therefore we can no longer report an error.
- log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ // Reset buffer we're ready to loop again
+ frame.Reset()
+ switch line.Device {
+ case "stdout":
+ if !query.Stdout {
continue
}
-
- if query.Timestamps {
- frame.WriteString(line.Time.Format(time.RFC3339))
- frame.WriteString(" ")
+ header[0] = 1
+ case "stderr":
+ if !query.Stderr {
+ continue
}
- frame.WriteString(line.Msg)
+ header[0] = 2
+ default:
+ // Logging and moving on is the best we can do here. We may have already sent
+ // a Status and Content-Type to client therefore we can no longer report an error.
+ log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ continue
+ }
- binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
- if _, err := w.Write(header[0:8]); err != nil {
- log.Errorf("unable to write log output header: %q", err)
- }
- if _, err := io.WriteString(w, frame.String()); err != nil {
- log.Errorf("unable to write frame string: %q", err)
- }
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
- }
+ if query.Timestamps {
+ frame.WriteString(line.Time.Format(time.RFC3339))
+ frame.WriteString(" ")
+ }
+ frame.WriteString(line.Msg)
+
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := io.WriteString(w, frame.String()); err != nil {
+ log.Errorf("unable to write frame string: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
}
}
}
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 5acc94153..9d5cb5045 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -1,9 +1,10 @@
package compat
import (
- "context"
+ "encoding/json"
"fmt"
"net/http"
+ "sync"
"github.com/containers/libpod/v2/libpod"
"github.com/containers/libpod/v2/libpod/events"
@@ -15,77 +16,132 @@ import (
"github.com/sirupsen/logrus"
)
+// filtersFromRequests extracts the "filters" parameter from the specified
+// http.Request. The paramater can either be a `map[string][]string` as done
+// in new versions of Docker and libpod, or a `map[string]map[string]bool` as
+// done in older versions of Docker. We have to do a bit of Yoga to support
+// both - just as Docker does as well.
+//
+// Please refer to https://github.com/containers/podman/issues/6899 for some
+// background.
+func filtersFromRequest(r *http.Request) ([]string, error) {
+ var (
+ compatFilters map[string]map[string]bool
+ filters map[string][]string
+ libpodFilters []string
+ )
+ raw := []byte(r.Form.Get("filters"))
+
+ // Backwards compat with older versions of Docker.
+ if err := json.Unmarshal(raw, &compatFilters); err == nil {
+ for filterKey, filterMap := range compatFilters {
+ for filterValue, toAdd := range filterMap {
+ if toAdd {
+ libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue))
+ }
+ }
+ }
+ return libpodFilters, nil
+ }
+
+ if err := json.Unmarshal(raw, &filters); err != nil {
+ return nil, err
+ }
+
+ for filterKey, filterSlice := range filters {
+ for _, filterValue := range filterSlice {
+ libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue))
+ }
+ }
+
+ return libpodFilters, nil
+}
+
+// NOTE: this endpoint serves both the docker-compatible one and the new libpod
+// one.
func GetEvents(w http.ResponseWriter, r *http.Request) {
var (
- fromStart bool
- eventsError error
- decoder = r.Context().Value("decoder").(*schema.Decoder)
- runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ fromStart bool
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level
)
+ // NOTE: the "filters" parameter is extracted separately for backwards
+ // compat via `fitlerFromRequest()`.
query := struct {
- Since string `schema:"since"`
- Until string `schema:"until"`
- Filters map[string][]string `schema:"filters"`
- Stream bool `schema:"stream"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Stream bool `schema:"stream"`
}{
Stream: true,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- }
-
- var libpodFilters = []string{}
- if _, found := r.URL.Query()["filters"]; found {
- for k, v := range query.Filters {
- libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
- }
+ return
}
if len(query.Since) > 0 || len(query.Until) > 0 {
fromStart = true
}
- eventCtx, eventCancel := context.WithCancel(r.Context())
- eventChannel := make(chan *events.Event)
- go func() {
- readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until}
- eventsError = runtime.Events(eventCtx, readOpts)
- }()
- if eventsError != nil {
- utils.InternalServerError(w, eventsError)
- eventCancel()
- close(eventChannel)
+ libpodFilters, err := filtersFromRequest(r)
+ if err != nil {
+ utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- // If client disappears we need to stop listening for events
- go func(done <-chan struct{}) {
- <-done
- eventCancel()
- if _, ok := <-eventChannel; ok {
- close(eventChannel)
+ eventChannel := make(chan *events.Event)
+ errorChannel := make(chan error)
+
+ // Start reading events.
+ go func() {
+ readOpts := events.ReadOptions{
+ FromStart: fromStart,
+ Stream: query.Stream,
+ Filters: libpodFilters,
+ EventChannel: eventChannel,
+ Since: query.Since,
+ Until: query.Until,
}
- }(r.Context().Done())
+ errorChannel <- runtime.Events(r.Context(), readOpts)
+ }()
- // Headers need to be written out before turning Writer() over to json encoder
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
- }
+ var coder *jsoniter.Encoder
+ var writeHeader sync.Once
- json := jsoniter.ConfigCompatibleWithStandardLibrary
- coder := json.NewEncoder(w)
- coder.SetEscapeHTML(true)
+ for stream := true; stream; stream = query.Stream {
+ select {
+ case err := <-errorChannel:
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ case evt := <-eventChannel:
+ writeHeader.Do(func() {
+ // Use a sync.Once so that we write the header
+ // only once.
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ coder = json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+ })
- for event := range eventChannel {
- e := entities.ConvertToEntitiesEvent(*event)
- if err := coder.Encode(e); err != nil {
- logrus.Errorf("unable to write json: %q", err)
- }
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
+ if evt == nil {
+ continue
+ }
+
+ e := entities.ConvertToEntitiesEvent(*evt)
+ if err := coder.Encode(e); err != nil {
+ logrus.Errorf("unable to write json: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
}
+
}
}
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go
index aee4196dd..a3b8cb573 100644
--- a/pkg/api/handlers/compat/exec.go
+++ b/pkg/api/handlers/compat/exec.go
@@ -173,21 +173,11 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
return
}
- // Hijack the connection
- hijacker, ok := w.(http.Hijacker)
- if !ok {
- utils.InternalServerError(w, errors.Errorf("unable to hijack connection"))
- return
- }
-
- connection, buffer, err := hijacker.Hijack()
+ connection, buffer, err := AttachConnection(w, r)
if err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection"))
+ utils.InternalServerError(w, err)
return
}
-
- fmt.Fprintf(connection, AttachHeader)
-
logrus.Debugf("Hijack for attach of container %s exec session %s successful", sessionCtr.ID(), sessionID)
if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, connection, buffer, nil, nil, nil); err != nil {