summaryrefslogtreecommitdiff
path: root/pkg/api/handlers/generic/containers.go
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-01-10 21:28:05 +0100
committerGitHub <noreply@github.com>2020-01-10 21:28:05 +0100
commite1ffac6cc73eb36640cbaf6a1a28ba44749a96d9 (patch)
tree61134dad4b2efdaa65dee6e7206e5ce20080302c /pkg/api/handlers/generic/containers.go
parent6ed88e047579bd2d1eac99a6089cc617f0c4773d (diff)
parent986feef2e80cfaed7cbce0df4fd5d619bcffefd7 (diff)
downloadpodman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.tar.gz
podman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.tar.bz2
podman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.zip
Merge pull request #4832 from baude/apiv2tomaster
Apiv2tomaster
Diffstat (limited to 'pkg/api/handlers/generic/containers.go')
-rw-r--r--pkg/api/handlers/generic/containers.go306
1 files changed, 306 insertions, 0 deletions
diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/generic/containers.go
new file mode 100644
index 000000000..5a0a51fd7
--- /dev/null
+++ b/pkg/api/handlers/generic/containers.go
@@ -0,0 +1,306 @@
+package generic
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ Vols bool `schema:"v"`
+ Link bool `schema:"link"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if query.Link {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ utils.ErrLinkNotSupport)
+ return
+ }
+ utils.RemoveContainer(w, r, query.Force, query.Vols)
+}
+
+func ListContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ infoData, err := runtime.Info()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain system info"))
+ return
+ }
+
+ var list = make([]*handlers.Container, len(containers))
+ for i, ctnr := range containers {
+ api, err := handlers.LibpodToContainer(ctnr, infoData)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ list[i] = api
+ }
+ utils.WriteResponse(w, http.StatusOK, list)
+}
+
+func GetContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ api, err := handlers.LibpodToContainerJSON(ctnr)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, api)
+}
+
+func KillContainer(w http.ResponseWriter, r *http.Request) {
+ // /{version}/containers/(name)/kill
+ con, err := utils.KillContainer(w, r)
+ if err != nil {
+ return
+ }
+ // the kill behavior for docker differs from podman in that they appear to wait
+ // for the Container to croak so the exit code is accurate immediately after the
+ // kill is sent. libpod does not. but we can add a wait here only for the docker
+ // side of things and mimic that behavior
+ if _, err = con.Wait(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func WaitContainer(w http.ResponseWriter, r *http.Request) {
+ var msg string
+ // /{version}/containers/(name)/wait
+ exitCode, err := utils.WaitContainer(w, r)
+ if err != nil {
+ msg = err.Error()
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
+ StatusCode: int(exitCode),
+ Error: struct {
+ Message string
+ }{
+ Message: msg,
+ },
+ })
+}
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ deletedContainers := []string{}
+ var spaceReclaimed uint64
+ for _, ctnr := range containers {
+ // Only remove stopped or exit'ed containers.
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ switch state {
+ case define.ContainerStateStopped, define.ContainerStateExited:
+ default:
+ continue
+ }
+
+ deletedContainers = append(deletedContainers, ctnr.ID())
+ cSize, err := ctnr.RootFsSize()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ spaceReclaimed += uint64(cSize)
+
+ err = runtime.RemoveContainer(context.Background(), ctnr, false, false)
+ if err != nil && !(errors.Cause(err) == define.ErrCtrRemoved) {
+ utils.InternalServerError(w, err)
+ return
+ }
+ }
+ report := types.ContainersPruneReport{
+ ContainersDeleted: deletedContainers,
+ SpaceReclaimed: spaceReclaimed,
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Follow bool `schema:"follow"`
+ Stdout bool `schema:"stdout"`
+ Stderr bool `schema:"stderr"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Timestamps bool `schema:"timestamps"`
+ Tail string `schema:"tail"`
+ }{
+ Tail: "all",
+ }
+ 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
+ }
+
+ if !(query.Stdout || query.Stderr) {
+ msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
+ utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
+ return
+ }
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ var tail int64 = -1
+ if query.Tail != "all" {
+ tail, err = strconv.ParseInt(query.Tail, 0, 64)
+ if err != nil {
+ utils.BadRequest(w, "tail", query.Tail, err)
+ return
+ }
+ }
+
+ var since time.Time
+ if _, found := mux.Vars(r)["since"]; found {
+ since, err = util.ParseInputTime(query.Since)
+ if err != nil {
+ utils.BadRequest(w, "since", query.Since, err)
+ return
+ }
+ }
+
+ var until time.Time
+ if _, found := mux.Vars(r)["until"]; found {
+ since, err = util.ParseInputTime(query.Until)
+ if err != nil {
+ utils.BadRequest(w, "until", query.Until, err)
+ return
+ }
+ }
+
+ options := &logs.LogOptions{
+ Details: true,
+ Follow: query.Follow,
+ Since: since,
+ Tail: tail,
+ Timestamps: query.Timestamps,
+ }
+
+ var wg sync.WaitGroup
+ options.WaitGroup = &wg
+
+ logChannel := make(chan *logs.LogLine, tail+1)
+ if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ go func() {
+ wg.Wait()
+ close(logChannel)
+ }()
+
+ w.WriteHeader(http.StatusOK)
+ var builder strings.Builder
+ for ok := true; ok; ok = query.Follow {
+ for line := range logChannel {
+ if _, found := mux.Vars(r)["until"]; found {
+ if line.Time.After(until) {
+ break
+ }
+ }
+
+ // Reset variables we're ready to loop again
+ builder.Reset()
+ header := [8]byte{}
+
+ 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())
+ continue
+ }
+
+ if query.Timestamps {
+ builder.WriteString(line.Time.Format(time.RFC3339))
+ builder.WriteRune(' ')
+ }
+ builder.WriteString(line.Msg)
+
+ // Build header and output entry
+ binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len()))
+ if _, err := w.Write(header[:]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := fmt.Fprint(w, builder.String()); err != nil {
+ log.Errorf("unable to write builder string: %q", err)
+ }
+
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+ }
+}