summaryrefslogtreecommitdiff
path: root/pkg/api
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api')
-rw-r--r--pkg/api/handlers/compat/containers.go3
-rw-r--r--pkg/api/handlers/compat/containers_create.go2
-rw-r--r--pkg/api/handlers/compat/containers_logs.go21
-rw-r--r--pkg/api/handlers/compat/containers_stats.go43
-rw-r--r--pkg/api/handlers/libpod/containers_stats.go72
-rw-r--r--pkg/api/handlers/libpod/images_pull.go9
-rw-r--r--pkg/api/handlers/types.go2
-rw-r--r--pkg/api/server/handler_api.go17
-rw-r--r--pkg/api/server/register_containers.go31
9 files changed, 175 insertions, 25 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index f9e200ff3..3a904ba87 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -103,7 +103,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 {
+ if _, found := r.URL.Query()["limit"]; found && query.Limit > 0 {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
@@ -193,6 +193,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
err = con.Kill(signal)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
+ return
}
// Docker waits for the container to stop if the signal is 0 or
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 93e4fe540..1d0b4c45d 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -210,7 +210,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input
ImageID: newImage.ID(),
BuiltinImgVolumes: nil, // podman
ImageVolumeType: "", // podman
- Interactive: false,
+ Interactive: input.OpenStdin,
// IpcMode: input.HostConfig.IpcMode,
Labels: input.Labels,
LogDriver: input.HostConfig.LogConfig.Type, // is this correct
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go
index f6d4a518e..d24b7d959 100644
--- a/pkg/api/handlers/compat/containers_logs.go
+++ b/pkg/api/handlers/compat/containers_logs.go
@@ -105,6 +105,18 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var frame strings.Builder
header := make([]byte, 8)
+
+ writeHeader := true
+ // Docker does not write stream headers iff the container has a tty.
+ if !utils.IsLibpodRequest(r) {
+ inspectData, err := ctnr.Inspect(false)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ writeHeader = !inspectData.Config.Tty
+ }
+
for line := range logChannel {
if _, found := r.URL.Query()["until"]; found {
if line.Time.After(until) {
@@ -138,10 +150,13 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
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 writeHeader {
+ 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)
}
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index 3d7d49ad3..16bd0518a 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -75,32 +75,48 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
}
- for ok := true; ok; ok = query.Stream {
+ // Write header and content type.
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+
+ // Setup JSON encoder for streaming.
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+
+streamLabel: // A label to flatten the scope
+ select {
+ case <-r.Context().Done():
+ logrus.Debugf("Client connection (container stats) cancelled")
+
+ default:
// Container stats
stats, err := ctnr.GetContainerStats(stats)
if err != nil {
- utils.InternalServerError(w, err)
+ logrus.Errorf("Unable to get container stats: %v", err)
return
}
inspect, err := ctnr.Inspect(false)
if err != nil {
- utils.InternalServerError(w, err)
+ logrus.Errorf("Unable to inspect container: %v", err)
return
}
// Cgroup stats
cgroupPath, err := ctnr.CGroupPath()
if err != nil {
- utils.InternalServerError(w, err)
+ logrus.Errorf("Unable to get cgroup path of container: %v", err)
return
}
cgroup, err := cgroups.Load(cgroupPath)
if err != nil {
- utils.InternalServerError(w, err)
+ logrus.Errorf("Unable to load cgroup: %v", err)
return
}
cgroupStat, err := cgroup.Stat()
if err != nil {
- utils.InternalServerError(w, err)
+ logrus.Errorf("Unable to get cgroup stats: %v", err)
return
}
@@ -175,11 +191,18 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
Networks: net,
}
- utils.WriteJSON(w, http.StatusOK, s)
+ if err := coder.Encode(s); err != nil {
+ logrus.Errorf("Unable to encode stats: %v", err)
+ return
+ }
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
+ if !query.Stream {
+ return
+ }
+
preRead = s.Read
bits, err := json.Marshal(s.CPUStats)
if err != nil {
@@ -189,10 +212,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
logrus.Errorf("Unable to unmarshal previous stats: %q", err)
}
- // Only sleep when we're streaming.
- if query.Stream {
- time.Sleep(DefaultStatsPeriod)
- }
+ time.Sleep(DefaultStatsPeriod)
+ goto streamLabel
}
}
diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go
new file mode 100644
index 000000000..4d5abe118
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers_stats.go
@@ -0,0 +1,72 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const DefaultStatsPeriod = 5 * time.Second
+
+func StatsContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ Containers []string `schema:"containers"`
+ Stream bool `schema:"stream"`
+ }{
+ Stream: true,
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // Reduce code duplication and use the local/abi implementation of
+ // container stats.
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+
+ statsOptions := entities.ContainerStatsOptions{
+ Stream: query.Stream,
+ }
+
+ // Stats will stop if the connection is closed.
+ statsChan, err := containerEngine.ContainerStats(r.Context(), query.Containers, statsOptions)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Write header and content type.
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+
+ // Setup JSON encoder for streaming.
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+
+ for stats := range statsChan {
+ if err := coder.Encode(stats); err != nil {
+ // Note: even when streaming, the stats goroutine will
+ // be notified (and stop) as the connection will be
+ // closed.
+ logrus.Errorf("Unable to encode stats: %v", err)
+ return
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+}
diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go
index 8a2f4f4cf..ad8d1f38e 100644
--- a/pkg/api/handlers/libpod/images_pull.go
+++ b/pkg/api/handlers/libpod/images_pull.go
@@ -178,10 +178,19 @@ loop: // break out of for/select infinite loop
flush()
case <-runCtx.Done():
if !failed {
+ // Send all image id's pulled in 'images' stanza
report.Images = images
if err := enc.Encode(report); err != nil {
logrus.Warnf("Failed to json encode error %q", err.Error())
}
+
+ report.Images = nil
+ // Pull last ID from list and publish in 'id' stanza. This maintains previous API contract
+ report.ID = images[len(images)-1]
+ if err := enc.Encode(report); err != nil {
+ logrus.Warnf("Failed to json encode error %q", err.Error())
+ }
+
flush()
}
break loop // break out of for/select infinite loop
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 0ccaa95bb..9e503dbb0 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -33,7 +33,7 @@ type LibpodImagesLoadReport struct {
}
type LibpodImagesPullReport struct {
- ID string `json:"id"`
+ entities.ImagePullReport
}
// LibpodImagesRemoveReport is the return type for image removal via the rest
diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go
index f2ce0301b..920811c51 100644
--- a/pkg/api/server/handler_api.go
+++ b/pkg/api/server/handler_api.go
@@ -34,15 +34,18 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc {
}
// TODO: Use r.ConnContext when ported to go 1.13
- c := context.WithValue(r.Context(), "decoder", s.Decoder) //nolint
- c = context.WithValue(c, "runtime", s.Runtime) //nolint
- c = context.WithValue(c, "shutdownFunc", s.Shutdown) //nolint
- c = context.WithValue(c, "idletracker", s.idleTracker) //nolint
+ c := context.WithValue(r.Context(), "decoder", s.Decoder) // nolint
+ c = context.WithValue(c, "runtime", s.Runtime) // nolint
+ c = context.WithValue(c, "shutdownFunc", s.Shutdown) // nolint
+ c = context.WithValue(c, "idletracker", s.idleTracker) // nolint
r = r.WithContext(c)
- v := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion]
- w.Header().Set("API-Version", fmt.Sprintf("%d.%d", v.Major, v.Minor))
- w.Header().Set("Libpod-API-Version", utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String())
+ cv := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion]
+ w.Header().Set("API-Version", fmt.Sprintf("%d.%d", cv.Major, cv.Minor))
+
+ lv := utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String()
+ w.Header().Set("Libpod-API-Version", lv)
+ w.Header().Set("Server", "Libpod/"+lv+" ("+runtime.GOOS+")")
h(w, r)
}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 0ad5d29ea..870c6a90c 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -1013,7 +1013,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// tags:
// - containers
// summary: Get stats for a container
- // description: This returns a live stream of a container’s resource usage statistics.
+ // description: DEPRECATED. This endpoint will be removed with the next major release. Please use /libpod/containers/stats instead.
// parameters:
// - in: path
// name: name
@@ -1035,6 +1035,35 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/containers/stats libpod libpodStatsContainers
+ // ---
+ // tags:
+ // - containers
+ // summary: Get stats for one or more containers
+ // description: Return a live stream of resource usage statistics of one or more container. If no container is specified, the statistics of all containers are returned.
+ // parameters:
+ // - in: query
+ // name: containers
+ // description: names or IDs of containers
+ // type: array
+ // items:
+ // type: string
+ // - in: query
+ // name: stream
+ // type: boolean
+ // default: true
+ // description: Stream the output
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: no error
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/stats"), s.APIHandler(libpod.StatsContainer)).Methods(http.MethodGet)
+
// swagger:operation GET /libpod/containers/{name}/top libpod libpodTopContainer
// ---
// tags: