From 31112e4b087612f7d63e83d770263b8b9fa4f206 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 9 Mar 2020 14:18:44 +0100 Subject: Refactor handler packages To help with packaging, the handlers in pkg/api/handlers are now found in pkg/api/handler/compat. Signed-off-by: Jhon Honce --- pkg/api/handlers/compat/container_start.go | 60 +++++ pkg/api/handlers/compat/containers.go | 344 ++++++++++++++++++++++++ pkg/api/handlers/compat/containers_attach.go | 156 +++++++++++ pkg/api/handlers/compat/containers_create.go | 216 +++++++++++++++ pkg/api/handlers/compat/containers_pause.go | 28 ++ pkg/api/handlers/compat/containers_prune.go | 64 +++++ pkg/api/handlers/compat/containers_restart.go | 61 +++++ pkg/api/handlers/compat/containers_start.go | 51 ++++ pkg/api/handlers/compat/containers_stats.go | 211 +++++++++++++++ pkg/api/handlers/compat/containers_top.go | 54 ++++ pkg/api/handlers/compat/containers_unpause.go | 28 ++ pkg/api/handlers/compat/events.go | 68 +++++ pkg/api/handlers/compat/images.go | 362 ++++++++++++++++++++++++++ pkg/api/handlers/compat/images_build.go | 271 +++++++++++++++++++ pkg/api/handlers/compat/images_history.go | 40 +++ pkg/api/handlers/compat/images_remove.go | 52 ++++ pkg/api/handlers/compat/images_save.go | 14 + pkg/api/handlers/compat/images_search.go | 66 +++++ pkg/api/handlers/compat/images_tag.go | 37 +++ pkg/api/handlers/compat/info.go | 196 ++++++++++++++ pkg/api/handlers/compat/ping.go | 31 +++ pkg/api/handlers/compat/swagger.go | 27 ++ pkg/api/handlers/compat/system.go | 18 ++ pkg/api/handlers/compat/types.go | 55 ++++ pkg/api/handlers/compat/unsupported.go | 17 ++ pkg/api/handlers/compat/version.go | 69 +++++ pkg/api/handlers/containers.go | 243 ----------------- pkg/api/handlers/containers_attach.go | 156 ----------- pkg/api/handlers/containers_top.go | 53 ---- pkg/api/handlers/events.go | 61 ----- pkg/api/handlers/exec.go | 25 -- pkg/api/handlers/generic/containers.go | 295 --------------------- pkg/api/handlers/generic/containers_create.go | 216 --------------- pkg/api/handlers/generic/containers_stats.go | 211 --------------- pkg/api/handlers/generic/images.go | 362 -------------------------- pkg/api/handlers/generic/info.go | 196 -------------- pkg/api/handlers/generic/swagger.go | 27 -- pkg/api/handlers/generic/system.go | 18 -- pkg/api/handlers/generic/types.go | 55 ---- pkg/api/handlers/handler.go | 38 +-- pkg/api/handlers/images.go | 187 ------------- pkg/api/handlers/images_build.go | 267 ------------------- pkg/api/handlers/libpod/containers.go | 31 --- pkg/api/handlers/libpod/images.go | 2 +- pkg/api/handlers/ping.go | 30 --- pkg/api/handlers/unsupported.go | 17 -- pkg/api/handlers/utils/containers.go | 56 ---- pkg/api/handlers/utils/images.go | 5 + pkg/api/handlers/version.go | 73 ------ 49 files changed, 2605 insertions(+), 2615 deletions(-) create mode 100644 pkg/api/handlers/compat/container_start.go create mode 100644 pkg/api/handlers/compat/containers.go create mode 100644 pkg/api/handlers/compat/containers_attach.go create mode 100644 pkg/api/handlers/compat/containers_create.go create mode 100644 pkg/api/handlers/compat/containers_pause.go create mode 100644 pkg/api/handlers/compat/containers_prune.go create mode 100644 pkg/api/handlers/compat/containers_restart.go create mode 100644 pkg/api/handlers/compat/containers_start.go create mode 100644 pkg/api/handlers/compat/containers_stats.go create mode 100644 pkg/api/handlers/compat/containers_top.go create mode 100644 pkg/api/handlers/compat/containers_unpause.go create mode 100644 pkg/api/handlers/compat/events.go create mode 100644 pkg/api/handlers/compat/images.go create mode 100644 pkg/api/handlers/compat/images_build.go create mode 100644 pkg/api/handlers/compat/images_history.go create mode 100644 pkg/api/handlers/compat/images_remove.go create mode 100644 pkg/api/handlers/compat/images_save.go create mode 100644 pkg/api/handlers/compat/images_search.go create mode 100644 pkg/api/handlers/compat/images_tag.go create mode 100644 pkg/api/handlers/compat/info.go create mode 100644 pkg/api/handlers/compat/ping.go create mode 100644 pkg/api/handlers/compat/swagger.go create mode 100644 pkg/api/handlers/compat/system.go create mode 100644 pkg/api/handlers/compat/types.go create mode 100644 pkg/api/handlers/compat/unsupported.go create mode 100644 pkg/api/handlers/compat/version.go delete mode 100644 pkg/api/handlers/containers.go delete mode 100644 pkg/api/handlers/containers_attach.go delete mode 100644 pkg/api/handlers/containers_top.go delete mode 100644 pkg/api/handlers/events.go delete mode 100644 pkg/api/handlers/exec.go delete mode 100644 pkg/api/handlers/generic/containers.go delete mode 100644 pkg/api/handlers/generic/containers_create.go delete mode 100644 pkg/api/handlers/generic/containers_stats.go delete mode 100644 pkg/api/handlers/generic/images.go delete mode 100644 pkg/api/handlers/generic/info.go delete mode 100644 pkg/api/handlers/generic/swagger.go delete mode 100644 pkg/api/handlers/generic/system.go delete mode 100644 pkg/api/handlers/generic/types.go delete mode 100644 pkg/api/handlers/images.go delete mode 100644 pkg/api/handlers/images_build.go delete mode 100644 pkg/api/handlers/ping.go delete mode 100644 pkg/api/handlers/unsupported.go delete mode 100644 pkg/api/handlers/version.go (limited to 'pkg/api/handlers') diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/container_start.go new file mode 100644 index 000000000..d26ef2c82 --- /dev/null +++ b/pkg/api/handlers/compat/container_start.go @@ -0,0 +1,60 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func StopContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + // /{version}/containers/(name)/stop + query := struct { + Timeout int `schema:"t"` + }{ + // 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 + } + + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := con.State() + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "unable to get state for Container %s", name)) + return + } + // If the Container is stopped already, send a 304 + if state == define.ContainerStateStopped || state == define.ContainerStateExited { + utils.WriteResponse(w, http.StatusNotModified, "") + return + } + + var stopError error + if query.Timeout > 0 { + stopError = con.StopWithTimeout(uint(query.Timeout)) + } else { + stopError = con.Stop() + } + if stopError != nil { + utils.InternalServerError(w, errors.Wrapf(stopError, "failed to stop %s", name)) + return + } + + // Success + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go new file mode 100644 index 000000000..1298e7fa4 --- /dev/null +++ b/pkg/api/handlers/compat/containers.go @@ -0,0 +1,344 @@ +package compat + +import ( + "encoding/binary" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + "syscall" + "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/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.IsLibpodRequest(r) { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + utils.ErrLinkNotSupport) + return + } + + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func ListContainers(w http.ResponseWriter, r *http.Request) { + var ( + containers []*libpod.Container + err error + ) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + All bool `schema:"all"` + Limit int `schema:"limit"` + Size bool `schema:"size"` + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + 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.All { + containers, err = runtime.GetAllContainers() + } else { + containers, err = runtime.GetRunningContainers() + } + if err != nil { + utils.InternalServerError(w, err) + return + } + if _, found := r.URL.Query()["limit"]; found { + last := query.Limit + if len(containers) > last { + containers = containers[len(containers)-last:] + } + } + // TODO filters still need to be applied + 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, query.Size) + 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) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Size bool `schema:"size"` + }{ + // override any golang type defaults + } + + 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 + } + + name := utils.GetName(r) + ctnr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + api, err := handlers.LibpodToContainerJSON(ctnr, query.Size) + 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 + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Signal syscall.Signal `schema:"signal"` + }{ + Signal: syscall.SIGKILL, + } + 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 + } + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := con.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + + // If the Container is stopped already, send a 409 + if state == define.ContainerStateStopped || state == define.ContainerStateExited { + utils.Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name))) + return + } + + err = con.Kill(uint(query.Signal)) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) + } + + if utils.IsLibpodRequest(r) { + // 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 { + return + } + utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{ + StatusCode: int(exitCode), + Error: struct { + Message string + }{ + Message: msg, + }, + }) +} + +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 := utils.GetName(r) + 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 := r.URL.Query()["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 := r.URL.Query()["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 := r.URL.Query()["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() + } + } + } +} diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go new file mode 100644 index 000000000..da7b5bb0c --- /dev/null +++ b/pkg/api/handlers/compat/containers_attach.go @@ -0,0 +1,156 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/remotecommand" +) + +func AttachContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*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"` + }{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Error parsing parameters", 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, "Parameter conflict", http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true")) + return + } + + // TODO: Investigate supporting these. + // Logs replays container logs over the attach socket. + // Stream seems to break things up somehow? Not 100% clear. + if query.Logs { + utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the logs parameter to attach is not presently supported")) + return + } + // We only support stream=true or unset + if _, found := r.URL.Query()["stream"]; found && query.Stream { + utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported")) + 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 + } + if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { + utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) + return + } + + // Hijack the connection + hijacker, ok := w.(http.Hijacker) + if !ok { + utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) + return + } + + w.WriteHeader(http.StatusSwitchingProtocols) + + connection, buffer, err := hijacker.Hijack() + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + return + } + + logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) + + // Perform HTTP attach. + // HTTPAttach will handle everything about the connection from here on + // (including closing it and writing errors to it). + if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil); err != nil { + // We can't really do anything about errors anymore. HTTPAttach + // should be writing them to the connection. + logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err) + } + + logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) +} + +func ResizeContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + Height uint16 `schema:"h"` + Width uint16 `schema:"w"` + }{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + // This is not a 400, despite the fact that is should be, for + // compatibility reasons. + utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options")) + return + } + + name := utils.GetName(r) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + newSize := remotecommand.TerminalSize{ + Width: query.Width, + Height: query.Height, + } + if err := ctr.AttachResize(newSize); err != nil { + utils.InternalServerError(w, err) + return + } + // This is not a 204, even though we write nothing, for compatibility + // reasons. + utils.WriteResponse(w, http.StatusOK, "") +} diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go new file mode 100644 index 000000000..6b8440fc2 --- /dev/null +++ b/pkg/api/handlers/compat/containers_create.go @@ -0,0 +1,216 @@ +package compat + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + image2 "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/signal" + createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/storage" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func CreateContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + input := handlers.CreateContainerConfig{} + query := struct { + Name string `schema:"name"` + }{ + // 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 err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + if len(input.HostConfig.Links) > 0 { + utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) + return + } + cc, err := makeCreateConfig(input, newImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) + return + } + cc.Name = query.Name + utils.CreateContainer(r.Context(), w, runtime, &cc) +} + +func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { + var ( + err error + init bool + tmpfs []string + volumes []string + ) + env := make(map[string]string) + stopSignal := unix.SIGTERM + if len(input.StopSignal) > 0 { + stopSignal, err = signal.ParseSignal(input.StopSignal) + if err != nil { + return createconfig.CreateConfig{}, err + } + } + + workDir := "/" + if len(input.WorkingDir) > 0 { + workDir = input.WorkingDir + } + + stopTimeout := uint(define.CtrRemoveTimeout) + if input.StopTimeout != nil { + stopTimeout = uint(*input.StopTimeout) + } + c := createconfig.CgroupConfig{ + Cgroups: "", // podman + Cgroupns: "", // podman + CgroupParent: "", // podman + CgroupMode: "", // podman + } + security := createconfig.SecurityConfig{ + CapAdd: input.HostConfig.CapAdd, + CapDrop: input.HostConfig.CapDrop, + LabelOpts: nil, // podman + NoNewPrivs: false, // podman + ApparmorProfile: "", // podman + SeccompProfilePath: "", + SecurityOpts: input.HostConfig.SecurityOpt, + Privileged: input.HostConfig.Privileged, + ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, + ReadOnlyTmpfs: false, // podman-only + Sysctl: input.HostConfig.Sysctls, + } + + network := createconfig.NetworkConfig{ + DNSOpt: input.HostConfig.DNSOptions, + DNSSearch: input.HostConfig.DNSSearch, + DNSServers: input.HostConfig.DNS, + ExposedPorts: input.ExposedPorts, + HTTPProxy: false, // podman + IP6Address: "", + IPAddress: "", + LinkLocalIP: nil, // docker-only + MacAddress: input.MacAddress, + // NetMode: nil, + Network: input.HostConfig.NetworkMode.NetworkName(), + NetworkAlias: nil, // docker-only now + PortBindings: input.HostConfig.PortBindings, + Publish: nil, // podmanseccompPath + PublishAll: input.HostConfig.PublishAllPorts, + } + + uts := createconfig.UtsConfig{ + UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), + NoHosts: false, //podman + HostAdd: input.HostConfig.ExtraHosts, + Hostname: input.Hostname, + } + + z := createconfig.UserConfig{ + GroupAdd: input.HostConfig.GroupAdd, + IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, + UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), + User: input.User, + } + pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} + for k := range input.Volumes { + volumes = append(volumes, k) + } + + // Docker is more flexible about its input where podman throws + // away incorrectly formatted variables so we cannot reuse the + // parsing of the env input + // [Foo Other=one Blank=] + for _, e := range input.Env { + splitEnv := strings.Split(e, "=") + switch len(splitEnv) { + case 0: + continue + case 1: + env[splitEnv[0]] = "" + default: + env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") + } + } + + // format the tmpfs mounts into a []string from map + for k, v := range input.HostConfig.Tmpfs { + tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) + } + + if input.HostConfig.Init != nil && *input.HostConfig.Init { + init = true + } + + m := createconfig.CreateConfig{ + Annotations: nil, // podman + Args: nil, + Cgroup: c, + CidFile: "", + ConmonPidFile: "", // podman + Command: input.Cmd, + UserCommand: input.Cmd, // podman + Detach: false, // + // Devices: input.HostConfig.Devices, + Entrypoint: input.Entrypoint, + Env: env, + HealthCheck: nil, // + Init: init, + InitPath: "", // tbd + Image: input.Image, + ImageID: newImage.ID(), + BuiltinImgVolumes: nil, // podman + ImageVolumeType: "", // podman + Interactive: false, + // IpcMode: input.HostConfig.IpcMode, + Labels: input.Labels, + LogDriver: input.HostConfig.LogConfig.Type, // is this correct + // LogDriverOpt: input.HostConfig.LogConfig.Config, + Name: input.Name, + Network: network, + Pod: "", // podman + PodmanPath: "", // podman + Quiet: false, // front-end only + Resources: createconfig.CreateResourceConfig{}, + RestartPolicy: input.HostConfig.RestartPolicy.Name, + Rm: input.HostConfig.AutoRemove, + StopSignal: stopSignal, + StopTimeout: stopTimeout, + Systemd: false, // podman + Tmpfs: tmpfs, + User: z, + Uts: uts, + Tty: input.Tty, + Mounts: nil, // we populate + // MountsFlag: input.HostConfig.Mounts, + NamedVolumes: nil, // we populate + Volumes: volumes, + VolumesFrom: input.HostConfig.VolumesFrom, + WorkDir: workDir, + Rootfs: "", // podman + Security: security, + Syslog: false, // podman + + Pid: pidConfig, + } + return m, nil +} diff --git a/pkg/api/handlers/compat/containers_pause.go b/pkg/api/handlers/compat/containers_pause.go new file mode 100644 index 000000000..060bdbaeb --- /dev/null +++ b/pkg/api/handlers/compat/containers_pause.go @@ -0,0 +1,28 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +func PauseContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + // /{version}/containers/(name)/pause + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + // the api does not error if the Container is already paused, so just into it + if err := con.Pause(); err != nil { + utils.InternalServerError(w, err) + return + } + // Success + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go new file mode 100644 index 000000000..a56c3903d --- /dev/null +++ b/pkg/api/handlers/compat/containers_prune.go @@ -0,0 +1,64 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/docker/docker/api/types" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func PruneContainers(w http.ResponseWriter, r *http.Request) { + var ( + delContainers []string + space int64 + ) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + Filters map[string][]string `schema:"filters"` + }{} + 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 + } + + filterFuncs, err := utils.GenerateFilterFuncsFromMap(runtime, query.Filters) + if err != nil { + utils.InternalServerError(w, err) + return + } + prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs) + if err != nil { + utils.InternalServerError(w, err) + return + } + + // Libpod response differs + if utils.IsLibpodRequest(r) { + var response []handlers.LibpodContainersPruneReport + for ctrID, size := range prunedContainers { + response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size}) + } + for ctrID, err := range pruneErrors { + response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()}) + } + utils.WriteResponse(w, http.StatusOK, response) + return + } + for ctrID, size := range prunedContainers { + if pruneErrors[ctrID] == nil { + space += size + delContainers = append(delContainers, ctrID) + } + } + report := types.ContainersPruneReport{ + ContainersDeleted: delContainers, + SpaceReclaimed: uint64(space), + } + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go new file mode 100644 index 000000000..5b8fafaa4 --- /dev/null +++ b/pkg/api/handlers/compat/containers_restart.go @@ -0,0 +1,61 @@ +package compat + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func RestartContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + // /{version}/containers/(name)/restart + query := struct { + Timeout int `schema:"t"` + }{ + // Override golang default values for types + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.BadRequest(w, "url", r.URL.String(), errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := con.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + + // FIXME: This is not in the swagger.yml... + // If the Container is stopped already, send a 409 + if state == define.ContainerStateStopped || state == define.ContainerStateExited { + msg := fmt.Sprintf("Container %s is not running", name) + utils.Error(w, msg, http.StatusConflict, errors.New(msg)) + return + } + + timeout := con.StopTimeout() + if _, found := r.URL.Query()["t"]; found { + timeout = uint(query.Timeout) + } + + if err := con.RestartWithTimeout(r.Context(), timeout); err != nil { + utils.InternalServerError(w, err) + return + } + + // Success + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go new file mode 100644 index 000000000..67bd287ab --- /dev/null +++ b/pkg/api/handlers/compat/containers_start.go @@ -0,0 +1,51 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func StartContainer(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + DetachKeys string `schema:"detachKeys"` + }{ + // Override golang default values for types + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.BadRequest(w, "url", r.URL.String(), err) + return + } + if len(query.DetachKeys) > 0 { + // TODO - start does not support adding detach keys + utils.BadRequest(w, "detachKeys", query.DetachKeys, errors.New("the detachKeys parameter is not supported yet")) + return + } + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := con.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + if state == define.ContainerStateRunning { + utils.WriteResponse(w, http.StatusNotModified, "") + return + } + if err := con.Start(r.Context(), false); err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go new file mode 100644 index 000000000..53ad0a632 --- /dev/null +++ b/pkg/api/handlers/compat/containers_stats.go @@ -0,0 +1,211 @@ +package compat + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/cgroups" + docker "github.com/docker/docker/api/types" + "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 { + 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 + } + + name := utils.GetName(r) + ctnr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + // If the container isn't running, then let's not bother and return + // immediately. + state, err := ctnr.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + if state != define.ContainerStateRunning && !query.Stream { + utils.InternalServerError(w, define.ErrCtrStateInvalid) + return + } + + stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{}) + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name)) + return + } + + var preRead time.Time + var preCPUStats CPUStats + if query.Stream { + preRead = time.Now() + systemUsage, _ := cgroups.GetSystemCPUUsage() + preCPUStats = CPUStats{ + CPUUsage: docker.CPUUsage{ + TotalUsage: stats.CPUNano, + PercpuUsage: stats.PerCPU, + UsageInKernelmode: stats.CPUSystemNano, + UsageInUsermode: stats.CPUNano - stats.CPUSystemNano, + }, + CPU: stats.CPU, + SystemUsage: systemUsage, + OnlineCPUs: 0, + ThrottlingData: docker.ThrottlingData{}, + } + } + + for ok := true; ok; ok = query.Stream { + // Container stats + stats, err := ctnr.GetContainerStats(stats) + if err != nil { + utils.InternalServerError(w, err) + return + } + inspect, err := ctnr.Inspect(false) + if err != nil { + utils.InternalServerError(w, err) + return + } + // Cgroup stats + cgroupPath, err := ctnr.CGroupPath() + if err != nil { + utils.InternalServerError(w, err) + return + } + cgroup, err := cgroups.Load(cgroupPath) + if err != nil { + utils.InternalServerError(w, err) + return + } + cgroupStat, err := cgroup.Stat() + if err != nil { + utils.InternalServerError(w, err) + return + } + + // FIXME: network inspection does not yet work entirely + net := make(map[string]docker.NetworkStats) + networkName := inspect.NetworkSettings.EndpointID + if networkName == "" { + networkName = "network" + } + net[networkName] = docker.NetworkStats{ + RxBytes: stats.NetInput, + RxPackets: 0, + RxErrors: 0, + RxDropped: 0, + TxBytes: stats.NetOutput, + TxPackets: 0, + TxErrors: 0, + TxDropped: 0, + EndpointID: inspect.NetworkSettings.EndpointID, + InstanceID: "", + } + + systemUsage, _ := cgroups.GetSystemCPUUsage() + s := StatsJSON{ + Stats: Stats{ + Read: time.Now(), + PreRead: preRead, + PidsStats: docker.PidsStats{ + Current: cgroupStat.Pids.Current, + Limit: 0, + }, + BlkioStats: docker.BlkioStats{ + IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive), + IoServicedRecursive: nil, + IoQueuedRecursive: nil, + IoServiceTimeRecursive: nil, + IoWaitTimeRecursive: nil, + IoMergedRecursive: nil, + IoTimeRecursive: nil, + SectorsRecursive: nil, + }, + CPUStats: CPUStats{ + CPUUsage: docker.CPUUsage{ + TotalUsage: cgroupStat.CPU.Usage.Total, + PercpuUsage: cgroupStat.CPU.Usage.PerCPU, + UsageInKernelmode: cgroupStat.CPU.Usage.Kernel, + UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel, + }, + CPU: stats.CPU, + SystemUsage: systemUsage, + OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)), + ThrottlingData: docker.ThrottlingData{ + Periods: 0, + ThrottledPeriods: 0, + ThrottledTime: 0, + }, + }, + PreCPUStats: preCPUStats, + MemoryStats: docker.MemoryStats{ + Usage: cgroupStat.Memory.Usage.Usage, + MaxUsage: cgroupStat.Memory.Usage.Limit, + Stats: nil, + Failcnt: 0, + Limit: cgroupStat.Memory.Usage.Limit, + Commit: 0, + CommitPeak: 0, + PrivateWorkingSet: 0, + }, + }, + Name: stats.Name, + ID: stats.ContainerID, + Networks: net, + } + + utils.WriteJSON(w, http.StatusOK, s) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + preRead = s.Read + bits, err := json.Marshal(s.CPUStats) + if err != nil { + logrus.Errorf("Unable to marshal cpu stats: %q", err) + } + if err := json.Unmarshal(bits, &preCPUStats); err != nil { + logrus.Errorf("Unable to unmarshal previous stats: %q", err) + } + + // Only sleep when we're streaming. + if query.Stream { + time.Sleep(DefaultStatsPeriod) + } + } +} + +func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry { + results := make([]docker.BlkioStatEntry, len(entries)) + for i, e := range entries { + bits, err := json.Marshal(e) + if err != nil { + logrus.Errorf("unable to marshal blkio stats: %q", err) + } + if err := json.Unmarshal(bits, &results[i]); err != nil { + logrus.Errorf("unable to unmarshal blkio stats: %q", err) + } + } + return results +} diff --git a/pkg/api/handlers/compat/containers_top.go b/pkg/api/handlers/compat/containers_top.go new file mode 100644 index 000000000..202be55d1 --- /dev/null +++ b/pkg/api/handlers/compat/containers_top.go @@ -0,0 +1,54 @@ +package compat + +import ( + "net/http" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func TopContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + defaultValue := "-ef" + if utils.IsLibpodRequest(r) { + defaultValue = "" + } + query := struct { + PsArgs string `schema:"ps_args"` + }{ + PsArgs: defaultValue, + } + 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 + } + + name := utils.GetName(r) + c, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + output, err := c.Top([]string{query.PsArgs}) + if err != nil { + utils.InternalServerError(w, err) + return + } + + var body = handlers.ContainerTopOKBody{} + if len(output) > 0 { + body.Titles = strings.Split(output[0], "\t") + for _, line := range output[1:] { + body.Processes = append(body.Processes, strings.Split(line, "\t")) + } + } + utils.WriteJSON(w, http.StatusOK, body) +} diff --git a/pkg/api/handlers/compat/containers_unpause.go b/pkg/api/handlers/compat/containers_unpause.go new file mode 100644 index 000000000..adabdeaea --- /dev/null +++ b/pkg/api/handlers/compat/containers_unpause.go @@ -0,0 +1,28 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +func UnpauseContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + // /{version}/containers/(name)/unpause + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + if err := con.Unpause(); err != nil { + utils.InternalServerError(w, err) + return + } + + // Success + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go new file mode 100644 index 000000000..0f72ef328 --- /dev/null +++ b/pkg/api/handlers/compat/events.go @@ -0,0 +1,68 @@ +package compat + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +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) + ) + + query := struct { + Since string `schema:"since"` + Until string `schema:"until"` + Filters map[string][]string `schema:"filters"` + }{} + 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])) + } + } + + if len(query.Since) > 0 || len(query.Until) > 0 { + fromStart = true + } + eventChannel := make(chan *events.Event) + go func() { + readOpts := events.ReadOptions{FromStart: fromStart, Stream: true, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} + eventsError = runtime.Events(readOpts) + }() + if eventsError != nil { + utils.InternalServerError(w, eventsError) + return + } + + coder := json.NewEncoder(w) + coder.SetEscapeHTML(true) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + for event := range eventChannel { + e := handlers.EventToApiEvent(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() + } + } +} diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go new file mode 100644 index 000000000..b18687bf9 --- /dev/null +++ b/pkg/api/handlers/compat/images.go @@ -0,0 +1,362 @@ +package compat + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strings" + + "github.com/containers/buildah" + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/libpod" + image2 "github.com/containers/libpod/libpod/image" + "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/schema" + "github.com/pkg/errors" +) + +func ExportImage(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 server + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + name := utils.GetName(r) + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) + return + } + rdr, err := os.Open(tmpfile.Name()) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + return + } + defer rdr.Close() + defer os.Remove(tmpfile.Name()) + utils.WriteResponse(w, http.StatusOK, rdr) +} + +func PruneImages(w http.ResponseWriter, r *http.Request) { + var ( + filters []string + ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + + idr := []types.ImageDeleteResponseItem{} + for k, v := range query.Filters { + for _, val := range v { + filters = append(filters, fmt.Sprintf("%s=%s", k, val)) + } + } + pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + if err != nil { + utils.InternalServerError(w, err) + return + } + for _, p := range pruneCids { + idr = append(idr, types.ImageDeleteResponseItem{ + Deleted: p, + }) + } + + //FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden + ipr := types.ImagesPruneReport{ + ImagesDeleted: idr, + SpaceReclaimed: 1, // TODO we cannot supply this right now + } + utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr}) +} + +func CommitContainer(w http.ResponseWriter, r *http.Request) { + var ( + destImage string + ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Author string `schema:"author"` + Changes string `schema:"changes"` + Comment string `schema:"comment"` + Container string `schema:"container"` + //fromSrc string # fromSrc is currently unused + Pause bool `schema:"pause"` + Repo string `schema:"repo"` + Tag string `schema:"tag"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + rtc, err := runtime.GetConfig() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false) + tag := "latest" + options := libpod.ContainerCommitOptions{ + Pause: true, + } + options.CommitOptions = buildah.CommitOptions{ + SignaturePolicyPath: rtc.SignaturePolicyPath, + ReportWriter: os.Stderr, + SystemContext: sc, + PreferredManifestType: manifest.DockerV2Schema2MediaType, + } + + input := handlers.CreateContainerConfig{} + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + + if len(query.Tag) > 0 { + tag = query.Tag + } + options.Message = query.Comment + options.Author = query.Author + options.Pause = query.Pause + options.Changes = strings.Fields(query.Changes) + ctr, err := runtime.LookupContainer(query.Container) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) + return + } + + // I know mitr hates this ... but doing for now + if len(query.Repo) > 1 { + destImage = fmt.Sprintf("%s:%s", query.Repo, tag) + } + + commitImage, err := ctr.Commit(r.Context(), destImage, options) + if err != nil && !strings.Contains(err.Error(), "is not running") { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) + return + } + utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint +} + +func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { + // 200 no error + // 404 repo does not exist or no read access + // 500 internal + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + FromSrc string `schema:"fromSrc"` + Changes []string `schema:"changes"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image. + source := query.FromSrc + if source == "-" { + f, err := ioutil.TempFile("", "api_load.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) + return + } + source = f.Name() + if err := SaveFromBody(f, r); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) + } + } + iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) + return + } + tmpfile, err := ioutil.TempFile("", "fromsrc.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + // Success + utils.WriteResponse(w, http.StatusOK, struct { + Status string `json:"status"` + Progress string `json:"progress"` + ProgressDetail map[string]string `json:"progressDetail"` + Id string `json:"id"` + }{ + Status: iid, + ProgressDetail: map[string]string{}, + Id: iid, + }) + +} + +func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { + // 200 no error + // 404 repo does not exist or no read access + // 500 internal + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + FromImage string `schema:"fromImage"` + Tag string `schema:"tag"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + + /* + fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed. + repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image. + tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled. + */ + fromImage := query.FromImage + if len(query.Tag) >= 1 { + fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) + } + + // TODO + // We are eating the output right now because we haven't talked about how to deal with multiple responses yet + img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + + // Success + utils.WriteResponse(w, http.StatusOK, struct { + Status string `json:"status"` + Error string `json:"error"` + Progress string `json:"progress"` + ProgressDetail map[string]string `json:"progressDetail"` + Id string `json:"id"` + }{ + Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")), + ProgressDetail: map[string]string{}, + Id: img.ID(), + }) +} + +func GetImage(w http.ResponseWriter, r *http.Request) { + // 200 no error + // 404 no such + // 500 internal + name := utils.GetName(r) + newImage, err := utils.GetImage(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) + if err != nil { + utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID)) + return + } + utils.WriteResponse(w, http.StatusOK, inspect) +} + +func GetImages(w http.ResponseWriter, r *http.Request) { + images, err := utils.GetImages(w, r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) + return + } + var summaries = make([]*handlers.ImageSummary, len(images)) + for j, img := range images { + is, err := handlers.ImageToImageSummary(img) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) + return + } + summaries[j] = is + } + utils.WriteResponse(w, http.StatusOK, summaries) +} + +func LoadImages(w http.ResponseWriter, r *http.Request) { + // TODO this is basically wrong + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Changes map[string]string `json:"changes"` + Message string `json:"message"` + Quiet bool `json:"quiet"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + + var ( + err error + writer io.Writer + ) + f, err := ioutil.TempFile("", "api_load.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) + return + } + if err := SaveFromBody(f, r); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) + return + } + id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") + //id, err := runtime.Import(r.Context()) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) + return + } + utils.WriteResponse(w, http.StatusOK, struct { + Stream string `json:"stream"` + }{ + Stream: fmt.Sprintf("Loaded image: %s\n", id), + }) +} diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go new file mode 100644 index 000000000..e208e6ddc --- /dev/null +++ b/pkg/api/handlers/compat/images_build.go @@ -0,0 +1,271 @@ +package compat + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/buildah" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/storage/pkg/archive" + "github.com/gorilla/schema" +) + +func BuildImage(w http.ResponseWriter, r *http.Request) { + authConfigs := map[string]handlers.AuthConfig{} + if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { + authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) + if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { + utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) + return + } + } + + if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { + if hdr[0] != "application/x-tar" { + utils.BadRequest(w, "Content-Type", hdr[0], + fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) + } + } + + anchorDir, err := extractTarFile(r, w) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer os.RemoveAll(anchorDir) + + query := struct { + Dockerfile string `schema:"dockerfile"` + Tag string `schema:"t"` + ExtraHosts string `schema:"extrahosts"` + Remote string `schema:"remote"` + Quiet bool `schema:"q"` + NoCache bool `schema:"nocache"` + CacheFrom string `schema:"cachefrom"` + Pull bool `schema:"pull"` + Rm bool `schema:"rm"` + ForceRm bool `schema:"forcerm"` + Memory int64 `schema:"memory"` + MemSwap int64 `schema:"memswap"` + CpuShares uint64 `schema:"cpushares"` + CpuSetCpus string `schema:"cpusetcpus"` + CpuPeriod uint64 `schema:"cpuperiod"` + CpuQuota int64 `schema:"cpuquota"` + BuildArgs string `schema:"buildargs"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + Labels string `schema:"labels"` + NetworkMode string `schema:"networkmode"` + Platform string `schema:"platform"` + Target string `schema:"target"` + Outputs string `schema:"outputs"` + Registry string `schema:"registry"` + }{ + Dockerfile: "Dockerfile", + Tag: "", + ExtraHosts: "", + Remote: "", + Quiet: false, + NoCache: false, + CacheFrom: "", + Pull: false, + Rm: true, + ForceRm: false, + Memory: 0, + MemSwap: 0, + CpuShares: 0, + CpuSetCpus: "", + CpuPeriod: 0, + CpuQuota: 0, + BuildArgs: "", + ShmSize: 64 * 1024 * 1024, + Squash: false, + Labels: "", + NetworkMode: "", + Platform: "", + Target: "", + Outputs: "", + Registry: "docker.io", + } + decoder := r.Context().Value("decoder").(*schema.Decoder) + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + return + } + + var ( + // Tag is the name with optional tag... + name = query.Tag + tag = "latest" + ) + if strings.Contains(query.Tag, ":") { + tokens := strings.SplitN(query.Tag, ":", 2) + name = tokens[0] + tag = tokens[1] + } + + if _, found := r.URL.Query()["target"]; found { + name = query.Target + } + + var buildArgs = map[string]string{} + if _, found := r.URL.Query()["buildargs"]; found { + if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { + utils.BadRequest(w, "buildargs", query.BuildArgs, err) + return + } + } + + // convert label formats + var labels = []string{} + if _, found := r.URL.Query()["labels"]; found { + var m = map[string]string{} + if err := json.Unmarshal([]byte(query.Labels), &m); err != nil { + utils.BadRequest(w, "labels", query.Labels, err) + return + } + + for k, v := range m { + labels = append(labels, k+"="+v) + } + } + + pullPolicy := buildah.PullIfMissing + if _, found := r.URL.Query()["pull"]; found { + if query.Pull { + pullPolicy = buildah.PullAlways + } + } + + // build events will be recorded here + var ( + buildEvents = []string{} + progress = bytes.Buffer{} + ) + + buildOptions := imagebuildah.BuildOptions{ + ContextDirectory: filepath.Join(anchorDir, "build"), + PullPolicy: pullPolicy, + Registry: query.Registry, + IgnoreUnrecognizedInstructions: true, + Quiet: query.Quiet, + Isolation: buildah.IsolationChroot, + Runtime: "", + RuntimeArgs: nil, + TransientMounts: nil, + Compression: archive.Gzip, + Args: buildArgs, + Output: name, + AdditionalTags: []string{tag}, + Log: func(format string, args ...interface{}) { + buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) + }, + In: nil, + Out: &progress, + Err: &progress, + SignaturePolicyPath: "", + ReportWriter: &progress, + OutputFormat: buildah.Dockerv2ImageManifest, + SystemContext: nil, + NamespaceOptions: nil, + ConfigureNetwork: 0, + CNIPluginPath: "", + CNIConfigDir: "", + IDMappingOptions: nil, + AddCapabilities: nil, + DropCapabilities: nil, + CommonBuildOpts: &buildah.CommonBuildOptions{ + AddHost: nil, + CgroupParent: "", + CPUPeriod: query.CpuPeriod, + CPUQuota: query.CpuQuota, + CPUShares: query.CpuShares, + CPUSetCPUs: query.CpuSetCpus, + CPUSetMems: "", + HTTPProxy: false, + Memory: query.Memory, + DNSSearch: nil, + DNSServers: nil, + DNSOptions: nil, + MemorySwap: query.MemSwap, + LabelOpts: nil, + SeccompProfilePath: "", + ApparmorProfile: "", + ShmSize: strconv.Itoa(query.ShmSize), + Ulimit: nil, + Volumes: nil, + }, + DefaultMountsFilePath: "", + IIDFile: "", + Squash: query.Squash, + Labels: labels, + Annotations: nil, + OnBuild: nil, + Layers: false, + NoCache: query.NoCache, + RemoveIntermediateCtrs: query.Rm, + ForceRmIntermediateCtrs: query.ForceRm, + BlobDirectory: "", + Target: query.Target, + Devices: nil, + } + + runtime := r.Context().Value("runtime").(*libpod.Runtime) + id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile) + if err != nil { + utils.InternalServerError(w, err) + } + + // Find image ID that was built... + utils.WriteResponse(w, http.StatusOK, + struct { + Stream string `json:"stream"` + }{ + Stream: progress.String() + "\n" + + strings.Join(buildEvents, "\n") + + fmt.Sprintf("\nSuccessfully built %s\n", id), + }) +} + +func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { + // build a home for the request body + anchorDir, err := ioutil.TempDir("", "libpod_builder") + if err != nil { + return "", err + } + buildDir := filepath.Join(anchorDir, "build") + + path := filepath.Join(anchorDir, "tarBall") + tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return "", err + } + defer tarBall.Close() + + // Content-Length not used as too many existing API clients didn't honor it + _, err = io.Copy(tarBall, r.Body) + r.Body.Close() + + if err != nil { + utils.InternalServerError(w, + fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) + } + + _, _ = tarBall.Seek(0, 0) + if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil { + return "", err + } + return anchorDir, nil +} diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go new file mode 100644 index 000000000..04304caa4 --- /dev/null +++ b/pkg/api/handlers/compat/images_history.go @@ -0,0 +1,40 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +func HistoryImage(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := utils.GetName(r) + var allHistory []handlers.HistoryResponse + + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) + return + + } + history, err := newImage.History(r.Context()) + if err != nil { + utils.InternalServerError(w, err) + return + } + for _, h := range history { + l := handlers.HistoryResponse{ + ID: h.ID, + Created: h.Created.UnixNano(), + CreatedBy: h.CreatedBy, + Tags: h.Tags, + Size: h.Size, + Comment: h.Comment, + } + allHistory = append(allHistory, l) + } + utils.WriteResponse(w, http.StatusOK, allHistory) +} diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go new file mode 100644 index 000000000..3d346543e --- /dev/null +++ b/pkg/api/handlers/compat/images_remove.go @@ -0,0 +1,52 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func RemoveImage(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Force bool `schema:"force"` + NoPrune bool `schema:"noprune"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 _, found := r.URL.Query()["noprune"]; found { + if query.NoPrune { + utils.UnSupportedParameter("noprune") + } + } + name := utils.GetName(r) + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + + _, err = runtime.RemoveImage(r.Context(), newImage, query.Force) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + // TODO + // This will need to be fixed for proper response, like Deleted: and Untagged: + m := make(map[string]string) + m["Deleted"] = newImage.ID() + foo := []map[string]string{} + foo = append(foo, m) + utils.WriteResponse(w, http.StatusOK, foo) + +} diff --git a/pkg/api/handlers/compat/images_save.go b/pkg/api/handlers/compat/images_save.go new file mode 100644 index 000000000..b39c719a0 --- /dev/null +++ b/pkg/api/handlers/compat/images_save.go @@ -0,0 +1,14 @@ +package compat + +import ( + "io" + "net/http" + "os" +) + +func SaveFromBody(f *os.File, r *http.Request) error { // nolint + if _, err := io.Copy(f, r.Body); err != nil { + return err + } + return f.Close() +} diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go new file mode 100644 index 000000000..7283b22c4 --- /dev/null +++ b/pkg/api/handlers/compat/images_search.go @@ -0,0 +1,66 @@ +package compat + +import ( + "net/http" + "strconv" + + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func SearchImages(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Term string `json:"term"` + Limit int `json:"limit"` + Filters map[string][]string `json:"filters"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + + filter := image.SearchFilter{} + if len(query.Filters) > 0 { + if len(query.Filters["stars"]) > 0 { + stars, err := strconv.Atoi(query.Filters["stars"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.Stars = stars + } + if len(query.Filters["is-official"]) > 0 { + isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.IsOfficial = types.NewOptionalBool(isOfficial) + } + if len(query.Filters["is-automated"]) > 0 { + isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.IsAutomated = types.NewOptionalBool(isAutomated) + } + } + options := image.SearchOptions{ + Filter: filter, + Limit: query.Limit, + } + results, err := image.SearchImages(query.Term, options) + if err != nil { + utils.BadRequest(w, "term", query.Term, err) + return + } + utils.WriteResponse(w, http.StatusOK, results) +} diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go new file mode 100644 index 000000000..722be5653 --- /dev/null +++ b/pkg/api/handlers/compat/images_tag.go @@ -0,0 +1,37 @@ +package compat + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +func TagImage(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + // /v1.xx/images/(name)/tag + name := utils.GetName(r) + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + tag := "latest" + if len(r.Form.Get("tag")) > 0 { + tag = r.Form.Get("tag") + } + if len(r.Form.Get("repo")) < 1 { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) + return + } + repo := r.Form.Get("repo") + tagName := fmt.Sprintf("%s:%s", repo, tag) + if err := newImage.TagImage(tagName); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusCreated, "") +} diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go new file mode 100644 index 000000000..30b49948d --- /dev/null +++ b/pkg/api/handlers/compat/info.go @@ -0,0 +1,196 @@ +package compat + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + goRuntime "runtime" + "strings" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/config" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/sysinfo" + docker "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/google/uuid" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func GetInfo(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + infoData, err := runtime.Info() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) + return + } + hostInfo := infoData[0].Data + storeInfo := infoData[1].Data + + configInfo, err := runtime.GetConfig() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain runtime config")) + return + } + versionInfo, err := define.GetVersion() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain podman versions")) + return + } + stateInfo := getContainersState(runtime) + sysInfo := sysinfo.New(true) + + // FIXME: Need to expose if runtime supports Checkpoint'ing + // liveRestoreEnabled := criu.CheckForCriu() && configInfo.RuntimeSupportsCheckpoint() + + info := &handlers.Info{Info: docker.Info{ + Architecture: goRuntime.GOARCH, + BridgeNfIP6tables: !sysInfo.BridgeNFCallIP6TablesDisabled, + BridgeNfIptables: !sysInfo.BridgeNFCallIPTablesDisabled, + CPUCfsPeriod: sysInfo.CPUCfsPeriod, + CPUCfsQuota: sysInfo.CPUCfsQuota, + CPUSet: sysInfo.Cpuset, + CPUShares: sysInfo.CPUShares, + CgroupDriver: configInfo.CgroupManager, + ClusterAdvertise: "", + ClusterStore: "", + ContainerdCommit: docker.Commit{}, + Containers: storeInfo["ContainerStore"].(map[string]interface{})["number"].(int), + ContainersPaused: stateInfo[define.ContainerStatePaused], + ContainersRunning: stateInfo[define.ContainerStateRunning], + ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited], + Debug: log.IsLevelEnabled(log.DebugLevel), + DefaultRuntime: configInfo.OCIRuntime, + DockerRootDir: storeInfo["GraphRoot"].(string), + Driver: storeInfo["GraphDriverName"].(string), + DriverStatus: getGraphStatus(storeInfo), + ExperimentalBuild: true, + GenericResources: nil, + HTTPProxy: getEnv("http_proxy"), + HTTPSProxy: getEnv("https_proxy"), + ID: uuid.New().String(), + IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled, + Images: storeInfo["ImageStore"].(map[string]interface{})["number"].(int), + IndexServerAddress: "", + InitBinary: "", + InitCommit: docker.Commit{}, + Isolation: "", + KernelMemory: sysInfo.KernelMemory, + KernelMemoryTCP: false, + KernelVersion: hostInfo["kernel"].(string), + Labels: nil, + LiveRestoreEnabled: false, + LoggingDriver: "", + MemTotal: hostInfo["MemTotal"].(int64), + MemoryLimit: sysInfo.MemoryLimit, + NCPU: goRuntime.NumCPU(), + NEventsListener: 0, + NFd: getFdCount(), + NGoroutines: goRuntime.NumGoroutine(), + Name: hostInfo["hostname"].(string), + NoProxy: getEnv("no_proxy"), + OSType: goRuntime.GOOS, + OSVersion: hostInfo["Distribution"].(map[string]interface{})["version"].(string), + OomKillDisable: sysInfo.OomKillDisable, + OperatingSystem: hostInfo["Distribution"].(map[string]interface{})["distribution"].(string), + PidsLimit: sysInfo.PidsLimit, + Plugins: docker.PluginsInfo{}, + ProductLicense: "Apache-2.0", + RegistryConfig: nil, + RuncCommit: docker.Commit{}, + Runtimes: getRuntimes(configInfo), + SecurityOptions: getSecOpts(sysInfo), + ServerVersion: versionInfo.Version, + SwapLimit: sysInfo.SwapLimit, + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateInactive, + }, + SystemStatus: nil, + SystemTime: time.Now().Format(time.RFC3339Nano), + Warnings: []string{}, + }, + BuildahVersion: hostInfo["BuildahVersion"].(string), + CPURealtimePeriod: sysInfo.CPURealtimePeriod, + CPURealtimeRuntime: sysInfo.CPURealtimeRuntime, + CgroupVersion: hostInfo["CgroupVersion"].(string), + Rootless: rootless.IsRootless(), + SwapFree: hostInfo["SwapFree"].(int64), + SwapTotal: hostInfo["SwapTotal"].(int64), + Uptime: hostInfo["uptime"].(string), + } + utils.WriteResponse(w, http.StatusOK, info) +} + +func getGraphStatus(storeInfo map[string]interface{}) [][2]string { + var graphStatus [][2]string + for k, v := range storeInfo["GraphStatus"].(map[string]string) { + graphStatus = append(graphStatus, [2]string{k, v}) + } + return graphStatus +} + +func getSecOpts(sysInfo *sysinfo.SysInfo) []string { + var secOpts []string + if sysInfo.AppArmor { + secOpts = append(secOpts, "name=apparmor") + } + if sysInfo.Seccomp { + // FIXME: get profile name... + secOpts = append(secOpts, fmt.Sprintf("name=seccomp,profile=%s", "default")) + } + return secOpts +} + +func getRuntimes(configInfo *config.Config) map[string]docker.Runtime { + var runtimes = map[string]docker.Runtime{} + for name, paths := range configInfo.OCIRuntimes { + runtimes[name] = docker.Runtime{ + Path: paths[0], + Args: nil, + } + } + return runtimes +} + +func getFdCount() (count int) { + count = -1 + if entries, err := ioutil.ReadDir("/proc/self/fd"); err == nil { + count = len(entries) + } + return +} + +// Just ignoring Container errors here... +func getContainersState(r *libpod.Runtime) map[define.ContainerStatus]int { + var states = map[define.ContainerStatus]int{} + ctnrs, err := r.GetAllContainers() + if err == nil { + for _, ctnr := range ctnrs { + state, err := ctnr.State() + if err != nil { + continue + } + states[state] += 1 + } + } + return states +} + +func getEnv(value string) string { + if v, exists := os.LookupEnv(strings.ToUpper(value)); exists { + return v + } + if v, exists := os.LookupEnv(strings.ToLower(value)); exists { + return v + } + return "" +} diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go new file mode 100644 index 000000000..6e77e270f --- /dev/null +++ b/pkg/api/handlers/compat/ping.go @@ -0,0 +1,31 @@ +package compat + +import ( + "fmt" + "net/http" + + "github.com/containers/buildah" + "github.com/containers/libpod/pkg/api/handlers" +) + +// Ping returns headers to client about the service +// +// This handler must always be the same for the compatibility and libpod URL trees! +// Clients will use the Header availability to test which backend engine is in use. +func Ping(w http.ResponseWriter, r *http.Request) { + w.Header().Set("API-Version", handlers.DefaultApiVersion) + w.Header().Set("BuildKit-Version", "") + w.Header().Set("Docker-Experimental", "true") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Pragma", "no-cache") + + // API-Version and Libpod-API-Version may not always be equal + w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion) + w.Header().Set("Libpod-Buildha-Version", buildah.Version) + w.WriteHeader(http.StatusOK) + + if r.Method == http.MethodGet { + fmt.Fprint(w, "OK") + } + fmt.Fprint(w, "\n") +} diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go new file mode 100644 index 000000000..cbd8e61fb --- /dev/null +++ b/pkg/api/handlers/compat/swagger.go @@ -0,0 +1,27 @@ +package compat + +import ( + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +// Create container +// swagger:response ContainerCreateResponse +type swagCtrCreateResponse struct { + // in:body + Body struct { + utils.ContainerCreateResponse + } +} + +// Wait container +// swagger:response ContainerWaitResponse +type swagCtrWaitResponse struct { + // in:body + Body struct { + // container exit code + StatusCode int + Error struct { + Message string + } + } +} diff --git a/pkg/api/handlers/compat/system.go b/pkg/api/handlers/compat/system.go new file mode 100644 index 000000000..47e187ba1 --- /dev/null +++ b/pkg/api/handlers/compat/system.go @@ -0,0 +1,18 @@ +package compat + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + docker "github.com/docker/docker/api/types" +) + +func GetDiskUsage(w http.ResponseWriter, r *http.Request) { + utils.WriteResponse(w, http.StatusOK, handlers.DiskUsage{DiskUsage: docker.DiskUsage{ + LayersSize: 0, + Images: nil, + Containers: nil, + Volumes: nil, + }}) +} diff --git a/pkg/api/handlers/compat/types.go b/pkg/api/handlers/compat/types.go new file mode 100644 index 000000000..b8d06760f --- /dev/null +++ b/pkg/api/handlers/compat/types.go @@ -0,0 +1,55 @@ +package compat + +import ( + "time" + + docker "github.com/docker/docker/api/types" +) + +// CPUStats aggregates and wraps all CPU related info of container +type CPUStats struct { + // CPU Usage. Linux and Windows. + CPUUsage docker.CPUUsage `json:"cpu_usage"` + + // System Usage. Linux only. + SystemUsage uint64 `json:"system_cpu_usage,omitempty"` + + // Online CPUs. Linux only. + OnlineCPUs uint32 `json:"online_cpus,omitempty"` + + // Usage of CPU in %. Linux only. + CPU float64 `json:"cpu"` + + // Throttling Data. Linux only. + ThrottlingData docker.ThrottlingData `json:"throttling_data,omitempty"` +} + +// Stats is Ultimate struct aggregating all types of stats of one container +type Stats struct { + // Common stats + Read time.Time `json:"read"` + PreRead time.Time `json:"preread"` + + // Linux specific stats, not populated on Windows. + PidsStats docker.PidsStats `json:"pids_stats,omitempty"` + BlkioStats docker.BlkioStats `json:"blkio_stats,omitempty"` + + // Windows specific stats, not populated on Linux. + NumProcs uint32 `json:"num_procs"` + StorageStats docker.StorageStats `json:"storage_stats,omitempty"` + + // Shared stats + CPUStats CPUStats `json:"cpu_stats,omitempty"` + PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous" + MemoryStats docker.MemoryStats `json:"memory_stats,omitempty"` +} + +type StatsJSON struct { + Stats + + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + + // Networks request version >=1.21 + Networks map[string]docker.NetworkStats `json:"networks,omitempty"` +} diff --git a/pkg/api/handlers/compat/unsupported.go b/pkg/api/handlers/compat/unsupported.go new file mode 100644 index 000000000..d9c3c3f49 --- /dev/null +++ b/pkg/api/handlers/compat/unsupported.go @@ -0,0 +1,17 @@ +package compat + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/utils" + log "github.com/sirupsen/logrus" +) + +func UnsupportedHandler(w http.ResponseWriter, r *http.Request) { + msg := fmt.Sprintf("Path %s is not supported", r.URL.Path) + log.Infof("Request Failed: %s", msg) + + utils.WriteJSON(w, http.StatusInternalServerError, + utils.ErrorModel{Message: msg}) +} diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go new file mode 100644 index 000000000..c7f7917ac --- /dev/null +++ b/pkg/api/handlers/compat/version.go @@ -0,0 +1,69 @@ +package compat + +import ( + "fmt" + "net/http" + goRuntime "runtime" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + docker "github.com/docker/docker/api/types" + "github.com/pkg/errors" +) + +func VersionHandler(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + versionInfo, err := define.GetVersion() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + + infoData, err := runtime.Info() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) + return + } + hostInfo := infoData[0].Data + + components := []docker.ComponentVersion{{ + Name: "Podman Engine", + Version: versionInfo.Version, + Details: map[string]string{ + "APIVersion": handlers.DefaultApiVersion, + "Arch": goRuntime.GOARCH, + "BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339), + "Experimental": "true", + "GitCommit": versionInfo.GitCommit, + "GoVersion": versionInfo.GoVersion, + "KernelVersion": hostInfo["kernel"].(string), + "MinAPIVersion": handlers.MinimalApiVersion, + "Os": goRuntime.GOOS, + }, + }} + + utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{ + Platform: struct { + Name string + }{ + Name: fmt.Sprintf("%s/%s/%s", goRuntime.GOOS, goRuntime.GOARCH, hostInfo["Distribution"].(map[string]interface{})["distribution"].(string)), + }, + APIVersion: components[0].Details["APIVersion"], + Arch: components[0].Details["Arch"], + BuildTime: components[0].Details["BuildTime"], + Components: components, + Experimental: true, + GitCommit: components[0].Details["GitCommit"], + GoVersion: components[0].Details["GoVersion"], + KernelVersion: components[0].Details["KernelVersion"], + MinAPIVersion: components[0].Details["MinAPIVersion"], + Os: components[0].Details["Os"], + Version: components[0].Version, + }}) +} diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go deleted file mode 100644 index 1256256fd..000000000 --- a/pkg/api/handlers/containers.go +++ /dev/null @@ -1,243 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/docker/docker/api/types" - "github.com/gorilla/schema" - "github.com/pkg/errors" -) - -func StopContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - - // /{version}/containers/(name)/stop - query := struct { - Timeout int `schema:"t"` - }{ - // 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 - } - - name := utils.GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - state, err := con.State() - if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "unable to get state for Container %s", name)) - return - } - // If the Container is stopped already, send a 304 - if state == define.ContainerStateStopped || state == define.ContainerStateExited { - utils.WriteResponse(w, http.StatusNotModified, "") - return - } - - var stopError error - if query.Timeout > 0 { - stopError = con.StopWithTimeout(uint(query.Timeout)) - } else { - stopError = con.Stop() - } - if stopError != nil { - utils.InternalServerError(w, errors.Wrapf(stopError, "failed to stop %s", name)) - return - } - - // Success - utils.WriteResponse(w, http.StatusNoContent, "") -} - -func UnpauseContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - // /{version}/containers/(name)/unpause - name := utils.GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - if err := con.Unpause(); err != nil { - utils.InternalServerError(w, err) - return - } - - // Success - utils.WriteResponse(w, http.StatusNoContent, "") -} - -func PauseContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - // /{version}/containers/(name)/pause - name := utils.GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - // the api does not error if the Container is already paused, so just into it - if err := con.Pause(); err != nil { - utils.InternalServerError(w, err) - return - } - // Success - utils.WriteResponse(w, http.StatusNoContent, "") -} - -func StartContainer(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - DetachKeys string `schema:"detachKeys"` - }{ - // Override golang default values for types - } - 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 len(query.DetachKeys) > 0 { - // TODO - start does not support adding detach keys - utils.Error(w, "Something went wrong", http.StatusBadRequest, errors.New("the detachKeys parameter is not supported yet")) - return - } - runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := utils.GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - state, err := con.State() - if err != nil { - utils.InternalServerError(w, err) - return - } - if state == define.ContainerStateRunning { - utils.WriteResponse(w, http.StatusNotModified, "") - return - } - if err := con.Start(r.Context(), false); err != nil { - utils.InternalServerError(w, err) - return - } - utils.WriteResponse(w, http.StatusNoContent, "") -} - -func RestartContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - // /{version}/containers/(name)/restart - query := struct { - Timeout int `schema:"t"` - }{ - // Override golang default values for types - } - 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 - } - - name := utils.GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - state, err := con.State() - if err != nil { - utils.InternalServerError(w, err) - return - } - - // FIXME: This is not in the swagger.yml... - // If the Container is stopped already, send a 409 - if state == define.ContainerStateStopped || state == define.ContainerStateExited { - msg := fmt.Sprintf("Container %s is not running", name) - utils.Error(w, msg, http.StatusConflict, errors.New(msg)) - return - } - - timeout := con.StopTimeout() - if _, found := r.URL.Query()["t"]; found { - timeout = uint(query.Timeout) - } - - if err := con.RestartWithTimeout(r.Context(), timeout); err != nil { - utils.InternalServerError(w, err) - return - } - - // Success - utils.WriteResponse(w, http.StatusNoContent, "") -} - -func PruneContainers(w http.ResponseWriter, r *http.Request) { - var ( - delContainers []string - space int64 - ) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - - query := struct { - Filters map[string][]string `schema:"filter"` - }{} - 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 - } - - filterFuncs, err := utils.GenerateFilterFuncsFromMap(runtime, query.Filters) - if err != nil { - utils.InternalServerError(w, err) - return - } - prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs) - if err != nil { - utils.InternalServerError(w, err) - return - } - - // Libpod response differs - if utils.IsLibpodRequest(r) { - var response []LibpodContainersPruneReport - for ctrID, size := range prunedContainers { - response = append(response, LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size}) - } - for ctrID, err := range pruneErrors { - response = append(response, LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()}) - } - utils.WriteResponse(w, http.StatusOK, response) - return - } - for ctrID, size := range prunedContainers { - if pruneErrors[ctrID] == nil { - space += size - delContainers = append(delContainers, ctrID) - } - } - report := types.ContainersPruneReport{ - ContainersDeleted: delContainers, - SpaceReclaimed: uint64(space), - } - utils.WriteResponse(w, http.StatusOK, report) -} diff --git a/pkg/api/handlers/containers_attach.go b/pkg/api/handlers/containers_attach.go deleted file mode 100644 index 5a799a20c..000000000 --- a/pkg/api/handlers/containers_attach.go +++ /dev/null @@ -1,156 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/gorilla/schema" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "k8s.io/client-go/tools/remotecommand" -) - -func AttachContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*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"` - }{} - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, "Error parsing parameters", 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, "Parameter conflict", http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true")) - return - } - - // TODO: Investigate supporting these. - // Logs replays container logs over the attach socket. - // Stream seems to break things up somehow? Not 100% clear. - if query.Logs { - utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the logs parameter to attach is not presently supported")) - return - } - // We only support stream=true or unset - if _, found := r.URL.Query()["stream"]; found && query.Stream { - utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported")) - 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 - } - if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { - utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) - return - } - - // Hijack the connection - hijacker, ok := w.(http.Hijacker) - if !ok { - utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) - return - } - - w.WriteHeader(http.StatusSwitchingProtocols) - - connection, buffer, err := hijacker.Hijack() - if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) - return - } - - logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) - - // Perform HTTP attach. - // HTTPAttach will handle everything about the connection from here on - // (including closing it and writing errors to it). - if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil); err != nil { - // We can't really do anything about errors anymore. HTTPAttach - // should be writing them to the connection. - logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err) - } - - logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) -} - -func ResizeContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - - query := struct { - Height uint16 `schema:"h"` - Width uint16 `schema:"w"` - }{} - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - // This is not a 400, despite the fact that is should be, for - // compatibility reasons. - utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options")) - return - } - - name := utils.GetName(r) - ctr, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - newSize := remotecommand.TerminalSize{ - Width: query.Width, - Height: query.Height, - } - if err := ctr.AttachResize(newSize); err != nil { - utils.InternalServerError(w, err) - return - } - // This is not a 204, even though we write nothing, for compatibility - // reasons. - utils.WriteResponse(w, http.StatusOK, "") -} diff --git a/pkg/api/handlers/containers_top.go b/pkg/api/handlers/containers_top.go deleted file mode 100644 index 06d5dd653..000000000 --- a/pkg/api/handlers/containers_top.go +++ /dev/null @@ -1,53 +0,0 @@ -package handlers - -import ( - "net/http" - "strings" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/gorilla/schema" - "github.com/pkg/errors" -) - -func TopContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - - defaultValue := "-ef" - if utils.IsLibpodRequest(r) { - defaultValue = "" - } - query := struct { - PsArgs string `schema:"ps_args"` - }{ - PsArgs: defaultValue, - } - 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 - } - - name := utils.GetName(r) - c, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - output, err := c.Top([]string{query.PsArgs}) - if err != nil { - utils.InternalServerError(w, err) - return - } - - var body = ContainerTopOKBody{} - if len(output) > 0 { - body.Titles = strings.Split(output[0], "\t") - for _, line := range output[1:] { - body.Processes = append(body.Processes, strings.Split(line, "\t")) - } - } - utils.WriteJSON(w, http.StatusOK, body) -} diff --git a/pkg/api/handlers/events.go b/pkg/api/handlers/events.go deleted file mode 100644 index 22dad9923..000000000 --- a/pkg/api/handlers/events.go +++ /dev/null @@ -1,61 +0,0 @@ -package handlers - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/containers/libpod/libpod/events" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func GetEvents(w http.ResponseWriter, r *http.Request) { - var ( - fromStart bool - eventsError error - ) - query := struct { - Since string `schema:"since"` - Until string `schema:"until"` - Filters map[string][]string `schema:"filters"` - }{} - if err := decodeQuery(r, &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])) - } - } - - if len(query.Since) > 0 || len(query.Until) > 0 { - fromStart = true - } - eventChannel := make(chan *events.Event) - go func() { - readOpts := events.ReadOptions{FromStart: fromStart, Stream: true, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} - eventsError = getRuntime(r).Events(readOpts) - }() - if eventsError != nil { - utils.InternalServerError(w, eventsError) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - for event := range eventChannel { - e := EventToApiEvent(event) - //utils.WriteJSON(w, http.StatusOK, e) - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) - 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/exec.go b/pkg/api/handlers/exec.go deleted file mode 100644 index 8a7b2ae26..000000000 --- a/pkg/api/handlers/exec.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/utils" -) - -func CreateExec(w http.ResponseWriter, r *http.Request) { - utils.Error(w, "function not implemented", http.StatusInternalServerError, define.ErrNotImplemented) -} - -func StartExec(w http.ResponseWriter, r *http.Request) { - utils.Error(w, "function not implemented", http.StatusInternalServerError, define.ErrNotImplemented) -} - -func ResizeExec(w http.ResponseWriter, r *http.Request) { - utils.Error(w, "function not implemented", http.StatusInternalServerError, define.ErrNotImplemented) - -} - -func InspectExec(w http.ResponseWriter, r *http.Request) { - utils.Error(w, "function not implemented", http.StatusInternalServerError, define.ErrNotImplemented) -} diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/generic/containers.go deleted file mode 100644 index b8460702c..000000000 --- a/pkg/api/handlers/generic/containers.go +++ /dev/null @@ -1,295 +0,0 @@ -package generic - -import ( - "encoding/binary" - "fmt" - "net/http" - "strconv" - "strings" - "sync" - "time" - - "github.com/containers/libpod/libpod" - "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/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) { - var ( - containers []*libpod.Container - err error - ) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - All bool `schema:"all"` - Limit int `schema:"limit"` - Size bool `schema:"size"` - Filters map[string][]string `schema:"filters"` - }{ - // override any golang type defaults - } - - 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.All { - containers, err = runtime.GetAllContainers() - } else { - containers, err = runtime.GetRunningContainers() - } - if err != nil { - utils.InternalServerError(w, err) - return - } - if _, found := r.URL.Query()["limit"]; found { - last := query.Limit - if len(containers) > last { - containers = containers[len(containers)-last:] - } - } - // TODO filters still need to be applied - 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, query.Size) - 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) - decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - Size bool `schema:"size"` - }{ - // override any golang type defaults - } - - 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 - } - - name := utils.GetName(r) - ctnr, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - api, err := handlers.LibpodToContainerJSON(ctnr, query.Size) - 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 { - return - } - utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{ - StatusCode: int(exitCode), - Error: struct { - Message string - }{ - Message: msg, - }, - }) -} - -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 := utils.GetName(r) - 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 := r.URL.Query()["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 := r.URL.Query()["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 := r.URL.Query()["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() - } - } - } -} diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go deleted file mode 100644 index 7e542752f..000000000 --- a/pkg/api/handlers/generic/containers_create.go +++ /dev/null @@ -1,216 +0,0 @@ -package generic - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - image2 "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/api/handlers" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/signal" - createconfig "github.com/containers/libpod/pkg/spec" - "github.com/containers/storage" - "github.com/gorilla/schema" - "github.com/pkg/errors" - "golang.org/x/sys/unix" -) - -func CreateContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - input := handlers.CreateContainerConfig{} - query := struct { - Name string `schema:"name"` - }{ - // 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 err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) - return - } - if len(input.HostConfig.Links) > 0 { - utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) - } - newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) - return - } - cc, err := makeCreateConfig(input, newImage) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) - return - } - cc.Name = query.Name - utils.CreateContainer(r.Context(), w, runtime, &cc) -} - -func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { - var ( - err error - init bool - tmpfs []string - volumes []string - ) - env := make(map[string]string) - stopSignal := unix.SIGTERM - if len(input.StopSignal) > 0 { - stopSignal, err = signal.ParseSignal(input.StopSignal) - if err != nil { - return createconfig.CreateConfig{}, err - } - } - - workDir := "/" - if len(input.WorkingDir) > 0 { - workDir = input.WorkingDir - } - - stopTimeout := uint(define.CtrRemoveTimeout) - if input.StopTimeout != nil { - stopTimeout = uint(*input.StopTimeout) - } - c := createconfig.CgroupConfig{ - Cgroups: "", // podman - Cgroupns: "", // podman - CgroupParent: "", // podman - CgroupMode: "", // podman - } - security := createconfig.SecurityConfig{ - CapAdd: input.HostConfig.CapAdd, - CapDrop: input.HostConfig.CapDrop, - LabelOpts: nil, // podman - NoNewPrivs: false, // podman - ApparmorProfile: "", // podman - SeccompProfilePath: "", - SecurityOpts: input.HostConfig.SecurityOpt, - Privileged: input.HostConfig.Privileged, - ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, - ReadOnlyTmpfs: false, // podman-only - Sysctl: input.HostConfig.Sysctls, - } - - network := createconfig.NetworkConfig{ - DNSOpt: input.HostConfig.DNSOptions, - DNSSearch: input.HostConfig.DNSSearch, - DNSServers: input.HostConfig.DNS, - ExposedPorts: input.ExposedPorts, - HTTPProxy: false, // podman - IP6Address: "", - IPAddress: "", - LinkLocalIP: nil, // docker-only - MacAddress: input.MacAddress, - // NetMode: nil, - Network: input.HostConfig.NetworkMode.NetworkName(), - NetworkAlias: nil, // docker-only now - PortBindings: input.HostConfig.PortBindings, - Publish: nil, // podmanseccompPath - PublishAll: input.HostConfig.PublishAllPorts, - } - - uts := createconfig.UtsConfig{ - UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), - NoHosts: false, //podman - HostAdd: input.HostConfig.ExtraHosts, - Hostname: input.Hostname, - } - - z := createconfig.UserConfig{ - GroupAdd: input.HostConfig.GroupAdd, - IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, - UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), - User: input.User, - } - pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} - for k := range input.Volumes { - volumes = append(volumes, k) - } - - // Docker is more flexible about its input where podman throws - // away incorrectly formatted variables so we cannot reuse the - // parsing of the env input - // [Foo Other=one Blank=] - for _, e := range input.Env { - splitEnv := strings.Split(e, "=") - switch len(splitEnv) { - case 0: - continue - case 1: - env[splitEnv[0]] = "" - default: - env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") - } - } - - // format the tmpfs mounts into a []string from map - for k, v := range input.HostConfig.Tmpfs { - tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) - } - - if input.HostConfig.Init != nil && *input.HostConfig.Init { - init = true - } - - m := createconfig.CreateConfig{ - Annotations: nil, // podman - Args: nil, - Cgroup: c, - CidFile: "", - ConmonPidFile: "", // podman - Command: input.Cmd, - UserCommand: input.Cmd, // podman - Detach: false, // - // Devices: input.HostConfig.Devices, - Entrypoint: input.Entrypoint, - Env: env, - HealthCheck: nil, // - Init: init, - InitPath: "", // tbd - Image: input.Image, - ImageID: newImage.ID(), - BuiltinImgVolumes: nil, // podman - ImageVolumeType: "", // podman - Interactive: false, - // IpcMode: input.HostConfig.IpcMode, - Labels: input.Labels, - LogDriver: input.HostConfig.LogConfig.Type, // is this correct - // LogDriverOpt: input.HostConfig.LogConfig.Config, - Name: input.Name, - Network: network, - Pod: "", // podman - PodmanPath: "", // podman - Quiet: false, // front-end only - Resources: createconfig.CreateResourceConfig{}, - RestartPolicy: input.HostConfig.RestartPolicy.Name, - Rm: input.HostConfig.AutoRemove, - StopSignal: stopSignal, - StopTimeout: stopTimeout, - Systemd: false, // podman - Tmpfs: tmpfs, - User: z, - Uts: uts, - Tty: input.Tty, - Mounts: nil, // we populate - // MountsFlag: input.HostConfig.Mounts, - NamedVolumes: nil, // we populate - Volumes: volumes, - VolumesFrom: input.HostConfig.VolumesFrom, - WorkDir: workDir, - Rootfs: "", // podman - Security: security, - Syslog: false, // podman - - Pid: pidConfig, - } - return m, nil -} diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go deleted file mode 100644 index 977979741..000000000 --- a/pkg/api/handlers/generic/containers_stats.go +++ /dev/null @@ -1,211 +0,0 @@ -package generic - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/containers/libpod/pkg/cgroups" - docker "github.com/docker/docker/api/types" - "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 { - 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 - } - - name := utils.GetName(r) - ctnr, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - // If the container isn't running, then let's not bother and return - // immediately. - state, err := ctnr.State() - if err != nil { - utils.InternalServerError(w, err) - return - } - if state != define.ContainerStateRunning && !query.Stream { - utils.InternalServerError(w, define.ErrCtrStateInvalid) - return - } - - stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{}) - if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name)) - return - } - - var preRead time.Time - var preCPUStats CPUStats - if query.Stream { - preRead = time.Now() - systemUsage, _ := cgroups.GetSystemCPUUsage() - preCPUStats = CPUStats{ - CPUUsage: docker.CPUUsage{ - TotalUsage: stats.CPUNano, - PercpuUsage: stats.PerCPU, - UsageInKernelmode: stats.CPUSystemNano, - UsageInUsermode: stats.CPUNano - stats.CPUSystemNano, - }, - CPU: stats.CPU, - SystemUsage: systemUsage, - OnlineCPUs: 0, - ThrottlingData: docker.ThrottlingData{}, - } - } - - for ok := true; ok; ok = query.Stream { - // Container stats - stats, err := ctnr.GetContainerStats(stats) - if err != nil { - utils.InternalServerError(w, err) - return - } - inspect, err := ctnr.Inspect(false) - if err != nil { - utils.InternalServerError(w, err) - return - } - // Cgroup stats - cgroupPath, err := ctnr.CGroupPath() - if err != nil { - utils.InternalServerError(w, err) - return - } - cgroup, err := cgroups.Load(cgroupPath) - if err != nil { - utils.InternalServerError(w, err) - return - } - cgroupStat, err := cgroup.Stat() - if err != nil { - utils.InternalServerError(w, err) - return - } - - // FIXME: network inspection does not yet work entirely - net := make(map[string]docker.NetworkStats) - networkName := inspect.NetworkSettings.EndpointID - if networkName == "" { - networkName = "network" - } - net[networkName] = docker.NetworkStats{ - RxBytes: stats.NetInput, - RxPackets: 0, - RxErrors: 0, - RxDropped: 0, - TxBytes: stats.NetOutput, - TxPackets: 0, - TxErrors: 0, - TxDropped: 0, - EndpointID: inspect.NetworkSettings.EndpointID, - InstanceID: "", - } - - systemUsage, _ := cgroups.GetSystemCPUUsage() - s := StatsJSON{ - Stats: Stats{ - Read: time.Now(), - PreRead: preRead, - PidsStats: docker.PidsStats{ - Current: cgroupStat.Pids.Current, - Limit: 0, - }, - BlkioStats: docker.BlkioStats{ - IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive), - IoServicedRecursive: nil, - IoQueuedRecursive: nil, - IoServiceTimeRecursive: nil, - IoWaitTimeRecursive: nil, - IoMergedRecursive: nil, - IoTimeRecursive: nil, - SectorsRecursive: nil, - }, - CPUStats: CPUStats{ - CPUUsage: docker.CPUUsage{ - TotalUsage: cgroupStat.CPU.Usage.Total, - PercpuUsage: cgroupStat.CPU.Usage.PerCPU, - UsageInKernelmode: cgroupStat.CPU.Usage.Kernel, - UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel, - }, - CPU: stats.CPU, - SystemUsage: systemUsage, - OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)), - ThrottlingData: docker.ThrottlingData{ - Periods: 0, - ThrottledPeriods: 0, - ThrottledTime: 0, - }, - }, - PreCPUStats: preCPUStats, - MemoryStats: docker.MemoryStats{ - Usage: cgroupStat.Memory.Usage.Usage, - MaxUsage: cgroupStat.Memory.Usage.Limit, - Stats: nil, - Failcnt: 0, - Limit: cgroupStat.Memory.Usage.Limit, - Commit: 0, - CommitPeak: 0, - PrivateWorkingSet: 0, - }, - }, - Name: stats.Name, - ID: stats.ContainerID, - Networks: net, - } - - utils.WriteJSON(w, http.StatusOK, s) - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } - - preRead = s.Read - bits, err := json.Marshal(s.CPUStats) - if err != nil { - logrus.Errorf("Unable to marshal cpu stats: %q", err) - } - if err := json.Unmarshal(bits, &preCPUStats); err != nil { - logrus.Errorf("Unable to unmarshal previous stats: %q", err) - } - - // Only sleep when we're streaming. - if query.Stream { - time.Sleep(DefaultStatsPeriod) - } - } -} - -func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry { - results := make([]docker.BlkioStatEntry, len(entries)) - for i, e := range entries { - bits, err := json.Marshal(e) - if err != nil { - logrus.Errorf("unable to marshal blkio stats: %q", err) - } - if err := json.Unmarshal(bits, &results[i]); err != nil { - logrus.Errorf("unable to unmarshal blkio stats: %q", err) - } - } - return results -} diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go deleted file mode 100644 index 078896834..000000000 --- a/pkg/api/handlers/generic/images.go +++ /dev/null @@ -1,362 +0,0 @@ -package generic - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "strings" - - "github.com/containers/buildah" - "github.com/containers/image/v5/manifest" - "github.com/containers/libpod/libpod" - image2 "github.com/containers/libpod/libpod/image" - "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/schema" - "github.com/pkg/errors" -) - -func ExportImage(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 server - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) - return - } - tmpfile, err := ioutil.TempFile("", "api.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) - return - } - if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) - return - } - rdr, err := os.Open(tmpfile.Name()) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) - return - } - defer rdr.Close() - defer os.Remove(tmpfile.Name()) - utils.WriteResponse(w, http.StatusOK, rdr) -} - -func PruneImages(w http.ResponseWriter, r *http.Request) { - var ( - filters []string - ) - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - Filters map[string][]string `schema:"filters"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - - idr := []types.ImageDeleteResponseItem{} - for k, v := range query.Filters { - for _, val := range v { - filters = append(filters, fmt.Sprintf("%s=%s", k, val)) - } - } - pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) - if err != nil { - utils.InternalServerError(w, err) - return - } - for _, p := range pruneCids { - idr = append(idr, types.ImageDeleteResponseItem{ - Deleted: p, - }) - } - - //FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden - ipr := types.ImagesPruneReport{ - ImagesDeleted: idr, - SpaceReclaimed: 1, // TODO we cannot supply this right now - } - utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr}) -} - -func CommitContainer(w http.ResponseWriter, r *http.Request) { - var ( - destImage string - ) - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - Author string `schema:"author"` - Changes string `schema:"changes"` - Comment string `schema:"comment"` - Container string `schema:"container"` - //fromSrc string # fromSrc is currently unused - Pause bool `schema:"pause"` - Repo string `schema:"repo"` - Tag string `schema:"tag"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - rtc, err := runtime.GetConfig() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) - return - } - sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false) - tag := "latest" - options := libpod.ContainerCommitOptions{ - Pause: true, - } - options.CommitOptions = buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, - ReportWriter: os.Stderr, - SystemContext: sc, - PreferredManifestType: manifest.DockerV2Schema2MediaType, - } - - input := handlers.CreateContainerConfig{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) - return - } - - if len(query.Tag) > 0 { - tag = query.Tag - } - options.Message = query.Comment - options.Author = query.Author - options.Pause = query.Pause - options.Changes = strings.Fields(query.Changes) - ctr, err := runtime.LookupContainer(query.Container) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, err) - return - } - - // I know mitr hates this ... but doing for now - if len(query.Repo) > 1 { - destImage = fmt.Sprintf("%s:%s", query.Repo, tag) - } - - commitImage, err := ctr.Commit(r.Context(), destImage, options) - if err != nil && !strings.Contains(err.Error(), "is not running") { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) - return - } - utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint -} - -func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { - // 200 no error - // 404 repo does not exist or no read access - // 500 internal - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - FromSrc string `schema:"fromSrc"` - Changes []string `schema:"changes"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image. - source := query.FromSrc - if source == "-" { - f, err := ioutil.TempFile("", "api_load.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) - return - } - source = f.Name() - if err := handlers.SaveFromBody(f, r); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) - } - } - iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) - return - } - tmpfile, err := ioutil.TempFile("", "fromsrc.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) - return - } - // Success - utils.WriteResponse(w, http.StatusOK, struct { - Status string `json:"status"` - Progress string `json:"progress"` - ProgressDetail map[string]string `json:"progressDetail"` - Id string `json:"id"` - }{ - Status: iid, - ProgressDetail: map[string]string{}, - Id: iid, - }) - -} - -func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { - // 200 no error - // 404 repo does not exist or no read access - // 500 internal - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - FromImage string `schema:"fromImage"` - Tag string `schema:"tag"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - - /* - fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed. - repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image. - tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled. - */ - fromImage := query.FromImage - if len(query.Tag) >= 1 { - fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) - } - - // TODO - // We are eating the output right now because we haven't talked about how to deal with multiple responses yet - img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - - // Success - utils.WriteResponse(w, http.StatusOK, struct { - Status string `json:"status"` - Error string `json:"error"` - Progress string `json:"progress"` - ProgressDetail map[string]string `json:"progressDetail"` - Id string `json:"id"` - }{ - Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")), - ProgressDetail: map[string]string{}, - Id: img.ID(), - }) -} - -func GetImage(w http.ResponseWriter, r *http.Request) { - // 200 no error - // 404 no such - // 500 internal - name := utils.GetName(r) - newImage, err := handlers.GetImage(r, name) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) - return - } - inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) - if err != nil { - utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID)) - return - } - utils.WriteResponse(w, http.StatusOK, inspect) -} - -func GetImages(w http.ResponseWriter, r *http.Request) { - images, err := utils.GetImages(w, r) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) - return - } - var summaries = make([]*handlers.ImageSummary, len(images)) - for j, img := range images { - is, err := handlers.ImageToImageSummary(img) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) - return - } - summaries[j] = is - } - utils.WriteResponse(w, http.StatusOK, summaries) -} - -func LoadImages(w http.ResponseWriter, r *http.Request) { - // TODO this is basically wrong - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - Changes map[string]string `json:"changes"` - Message string `json:"message"` - Quiet bool `json:"quiet"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - - var ( - err error - writer io.Writer - ) - f, err := ioutil.TempFile("", "api_load.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) - return - } - if err := handlers.SaveFromBody(f, r); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) - return - } - id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") - //id, err := runtime.Import(r.Context()) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) - return - } - utils.WriteResponse(w, http.StatusOK, struct { - Stream string `json:"stream"` - }{ - Stream: fmt.Sprintf("Loaded image: %s\n", id), - }) -} diff --git a/pkg/api/handlers/generic/info.go b/pkg/api/handlers/generic/info.go deleted file mode 100644 index c9e79233d..000000000 --- a/pkg/api/handlers/generic/info.go +++ /dev/null @@ -1,196 +0,0 @@ -package generic - -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - goRuntime "runtime" - "strings" - "time" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/config" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/sysinfo" - docker "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/swarm" - "github.com/google/uuid" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" -) - -func GetInfo(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - infoData, err := runtime.Info() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) - return - } - hostInfo := infoData[0].Data - storeInfo := infoData[1].Data - - configInfo, err := runtime.GetConfig() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain runtime config")) - return - } - versionInfo, err := define.GetVersion() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain podman versions")) - return - } - stateInfo := getContainersState(runtime) - sysInfo := sysinfo.New(true) - - // FIXME: Need to expose if runtime supports Checkpoint'ing - // liveRestoreEnabled := criu.CheckForCriu() && configInfo.RuntimeSupportsCheckpoint() - - info := &handlers.Info{Info: docker.Info{ - Architecture: goRuntime.GOARCH, - BridgeNfIP6tables: !sysInfo.BridgeNFCallIP6TablesDisabled, - BridgeNfIptables: !sysInfo.BridgeNFCallIPTablesDisabled, - CPUCfsPeriod: sysInfo.CPUCfsPeriod, - CPUCfsQuota: sysInfo.CPUCfsQuota, - CPUSet: sysInfo.Cpuset, - CPUShares: sysInfo.CPUShares, - CgroupDriver: configInfo.CgroupManager, - ClusterAdvertise: "", - ClusterStore: "", - ContainerdCommit: docker.Commit{}, - Containers: storeInfo["ContainerStore"].(map[string]interface{})["number"].(int), - ContainersPaused: stateInfo[define.ContainerStatePaused], - ContainersRunning: stateInfo[define.ContainerStateRunning], - ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited], - Debug: log.IsLevelEnabled(log.DebugLevel), - DefaultRuntime: configInfo.OCIRuntime, - DockerRootDir: storeInfo["GraphRoot"].(string), - Driver: storeInfo["GraphDriverName"].(string), - DriverStatus: getGraphStatus(storeInfo), - ExperimentalBuild: true, - GenericResources: nil, - HTTPProxy: getEnv("http_proxy"), - HTTPSProxy: getEnv("https_proxy"), - ID: uuid.New().String(), - IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled, - Images: storeInfo["ImageStore"].(map[string]interface{})["number"].(int), - IndexServerAddress: "", - InitBinary: "", - InitCommit: docker.Commit{}, - Isolation: "", - KernelMemory: sysInfo.KernelMemory, - KernelMemoryTCP: false, - KernelVersion: hostInfo["kernel"].(string), - Labels: nil, - LiveRestoreEnabled: false, - LoggingDriver: "", - MemTotal: hostInfo["MemTotal"].(int64), - MemoryLimit: sysInfo.MemoryLimit, - NCPU: goRuntime.NumCPU(), - NEventsListener: 0, - NFd: getFdCount(), - NGoroutines: goRuntime.NumGoroutine(), - Name: hostInfo["hostname"].(string), - NoProxy: getEnv("no_proxy"), - OSType: goRuntime.GOOS, - OSVersion: hostInfo["Distribution"].(map[string]interface{})["version"].(string), - OomKillDisable: sysInfo.OomKillDisable, - OperatingSystem: hostInfo["Distribution"].(map[string]interface{})["distribution"].(string), - PidsLimit: sysInfo.PidsLimit, - Plugins: docker.PluginsInfo{}, - ProductLicense: "Apache-2.0", - RegistryConfig: nil, - RuncCommit: docker.Commit{}, - Runtimes: getRuntimes(configInfo), - SecurityOptions: getSecOpts(sysInfo), - ServerVersion: versionInfo.Version, - SwapLimit: sysInfo.SwapLimit, - Swarm: swarm.Info{ - LocalNodeState: swarm.LocalNodeStateInactive, - }, - SystemStatus: nil, - SystemTime: time.Now().Format(time.RFC3339Nano), - Warnings: []string{}, - }, - BuildahVersion: hostInfo["BuildahVersion"].(string), - CPURealtimePeriod: sysInfo.CPURealtimePeriod, - CPURealtimeRuntime: sysInfo.CPURealtimeRuntime, - CgroupVersion: hostInfo["CgroupVersion"].(string), - Rootless: rootless.IsRootless(), - SwapFree: hostInfo["SwapFree"].(int64), - SwapTotal: hostInfo["SwapTotal"].(int64), - Uptime: hostInfo["uptime"].(string), - } - utils.WriteResponse(w, http.StatusOK, info) -} - -func getGraphStatus(storeInfo map[string]interface{}) [][2]string { - var graphStatus [][2]string - for k, v := range storeInfo["GraphStatus"].(map[string]string) { - graphStatus = append(graphStatus, [2]string{k, v}) - } - return graphStatus -} - -func getSecOpts(sysInfo *sysinfo.SysInfo) []string { - var secOpts []string - if sysInfo.AppArmor { - secOpts = append(secOpts, "name=apparmor") - } - if sysInfo.Seccomp { - // FIXME: get profile name... - secOpts = append(secOpts, fmt.Sprintf("name=seccomp,profile=%s", "default")) - } - return secOpts -} - -func getRuntimes(configInfo *config.Config) map[string]docker.Runtime { - var runtimes = map[string]docker.Runtime{} - for name, paths := range configInfo.OCIRuntimes { - runtimes[name] = docker.Runtime{ - Path: paths[0], - Args: nil, - } - } - return runtimes -} - -func getFdCount() (count int) { - count = -1 - if entries, err := ioutil.ReadDir("/proc/self/fd"); err == nil { - count = len(entries) - } - return -} - -// Just ignoring Container errors here... -func getContainersState(r *libpod.Runtime) map[define.ContainerStatus]int { - var states = map[define.ContainerStatus]int{} - ctnrs, err := r.GetAllContainers() - if err == nil { - for _, ctnr := range ctnrs { - state, err := ctnr.State() - if err != nil { - continue - } - states[state] += 1 - } - } - return states -} - -func getEnv(value string) string { - if v, exists := os.LookupEnv(strings.ToUpper(value)); exists { - return v - } - if v, exists := os.LookupEnv(strings.ToLower(value)); exists { - return v - } - return "" -} diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go deleted file mode 100644 index c9c9610bb..000000000 --- a/pkg/api/handlers/generic/swagger.go +++ /dev/null @@ -1,27 +0,0 @@ -package generic - -import ( - "github.com/containers/libpod/pkg/api/handlers/utils" -) - -// Create container -// swagger:response ContainerCreateResponse -type swagCtrCreateResponse struct { - // in:body - Body struct { - utils.ContainerCreateResponse - } -} - -// Wait container -// swagger:response ContainerWaitResponse -type swagCtrWaitResponse struct { - // in:body - Body struct { - // container exit code - StatusCode int - Error struct { - Message string - } - } -} diff --git a/pkg/api/handlers/generic/system.go b/pkg/api/handlers/generic/system.go deleted file mode 100644 index edf1f8522..000000000 --- a/pkg/api/handlers/generic/system.go +++ /dev/null @@ -1,18 +0,0 @@ -package generic - -import ( - "net/http" - - "github.com/containers/libpod/pkg/api/handlers" - "github.com/containers/libpod/pkg/api/handlers/utils" - docker "github.com/docker/docker/api/types" -) - -func GetDiskUsage(w http.ResponseWriter, r *http.Request) { - utils.WriteResponse(w, http.StatusOK, handlers.DiskUsage{DiskUsage: docker.DiskUsage{ - LayersSize: 0, - Images: nil, - Containers: nil, - Volumes: nil, - }}) -} diff --git a/pkg/api/handlers/generic/types.go b/pkg/api/handlers/generic/types.go deleted file mode 100644 index f068ac011..000000000 --- a/pkg/api/handlers/generic/types.go +++ /dev/null @@ -1,55 +0,0 @@ -package generic - -import ( - "time" - - docker "github.com/docker/docker/api/types" -) - -// CPUStats aggregates and wraps all CPU related info of container -type CPUStats struct { - // CPU Usage. Linux and Windows. - CPUUsage docker.CPUUsage `json:"cpu_usage"` - - // System Usage. Linux only. - SystemUsage uint64 `json:"system_cpu_usage,omitempty"` - - // Online CPUs. Linux only. - OnlineCPUs uint32 `json:"online_cpus,omitempty"` - - // Usage of CPU in %. Linux only. - CPU float64 `json:"cpu"` - - // Throttling Data. Linux only. - ThrottlingData docker.ThrottlingData `json:"throttling_data,omitempty"` -} - -// Stats is Ultimate struct aggregating all types of stats of one container -type Stats struct { - // Common stats - Read time.Time `json:"read"` - PreRead time.Time `json:"preread"` - - // Linux specific stats, not populated on Windows. - PidsStats docker.PidsStats `json:"pids_stats,omitempty"` - BlkioStats docker.BlkioStats `json:"blkio_stats,omitempty"` - - // Windows specific stats, not populated on Linux. - NumProcs uint32 `json:"num_procs"` - StorageStats docker.StorageStats `json:"storage_stats,omitempty"` - - // Shared stats - CPUStats CPUStats `json:"cpu_stats,omitempty"` - PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous" - MemoryStats docker.MemoryStats `json:"memory_stats,omitempty"` -} - -type StatsJSON struct { - Stats - - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - - // Networks request version >=1.21 - Networks map[string]docker.NetworkStats `json:"networks,omitempty"` -} diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go index 231c11f23..2dd2c886b 100644 --- a/pkg/api/handlers/handler.go +++ b/pkg/api/handlers/handler.go @@ -1,38 +1,6 @@ package handlers -import ( - "net/http" - - "github.com/containers/libpod/libpod" - "github.com/gorilla/schema" - "github.com/pkg/errors" +const ( + DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/ + MinimalApiVersion = "1.24" ) - -// Convenience routines to reduce boiler plate in handlers - -// func hasVar(r *http.Request, k string) bool { -// _, found := mux.Vars(r)[k] -// return found -// } - -func decodeQuery(r *http.Request, i interface{}) error { - decoder := r.Context().Value("decoder").(*schema.Decoder) - - if err := decoder.Decode(i, r.URL.Query()); err != nil { - return errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()) - } - return nil -} - -func getRuntime(r *http.Request) *libpod.Runtime { - return r.Context().Value("runtime").(*libpod.Runtime) -} - -// func getHeader(r *http.Request, k string) string { -// return r.Header.Get(k) -// } -// -// func hasHeader(r *http.Request, k string) bool { -// _, found := r.Header[k] -// return found -// } diff --git a/pkg/api/handlers/images.go b/pkg/api/handlers/images.go deleted file mode 100644 index d4549e5b4..000000000 --- a/pkg/api/handlers/images.go +++ /dev/null @@ -1,187 +0,0 @@ -package handlers - -import ( - "fmt" - "io" - "net/http" - "os" - "strconv" - - "github.com/containers/image/v5/types" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/gorilla/schema" - "github.com/pkg/errors" -) - -func HistoryImage(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := utils.GetName(r) - var allHistory []HistoryResponse - - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) - return - - } - history, err := newImage.History(r.Context()) - if err != nil { - utils.InternalServerError(w, err) - return - } - for _, h := range history { - l := HistoryResponse{ - ID: h.ID, - Created: h.Created.UnixNano(), - CreatedBy: h.CreatedBy, - Tags: h.Tags, - Size: h.Size, - Comment: h.Comment, - } - allHistory = append(allHistory, l) - } - utils.WriteResponse(w, http.StatusOK, allHistory) -} - -func TagImage(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - // /v1.xx/images/(name)/tag - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) - return - } - tag := "latest" - if len(r.Form.Get("tag")) > 0 { - tag = r.Form.Get("tag") - } - if len(r.Form.Get("repo")) < 1 { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) - return - } - repo := r.Form.Get("repo") - tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.TagImage(tagName); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - utils.WriteResponse(w, http.StatusCreated, "") -} - -func RemoveImage(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - noPrune bool - }{ - // This is where you can override the golang default value for one of fields - } - - 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 _, found := r.URL.Query()["noprune"]; found { - if query.noPrune { - utils.UnSupportedParameter("noprune") - } - } - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) - return - } - - force := false - if len(r.Form.Get("force")) > 0 { - force, err = strconv.ParseBool(r.Form.Get("force")) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, err) - return - } - } - _, err = runtime.RemoveImage(r.Context(), newImage, force) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - // TODO - // This will need to be fixed for proper response, like Deleted: and Untagged: - m := make(map[string]string) - m["Deleted"] = newImage.ID() - foo := []map[string]string{} - foo = append(foo, m) - utils.WriteResponse(w, http.StatusOK, foo) - -} -func GetImage(r *http.Request, name string) (*image.Image, error) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - return runtime.ImageRuntime().NewFromLocal(name) -} - -func SaveFromBody(f *os.File, r *http.Request) error { // nolint - if _, err := io.Copy(f, r.Body); err != nil { - return err - } - return f.Close() -} - -func SearchImages(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - Term string `json:"term"` - Limit int `json:"limit"` - Filters map[string][]string `json:"filters"` - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - - filter := image.SearchFilter{} - if len(query.Filters) > 0 { - if len(query.Filters["stars"]) > 0 { - stars, err := strconv.Atoi(query.Filters["stars"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.Stars = stars - } - if len(query.Filters["is-official"]) > 0 { - isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsOfficial = types.NewOptionalBool(isOfficial) - } - if len(query.Filters["is-automated"]) > 0 { - isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsAutomated = types.NewOptionalBool(isAutomated) - } - } - options := image.SearchOptions{ - Filter: filter, - Limit: query.Limit, - } - results, err := image.SearchImages(query.Term, options) - if err != nil { - utils.BadRequest(w, "term", query.Term, err) - return - } - utils.WriteResponse(w, http.StatusOK, results) -} diff --git a/pkg/api/handlers/images_build.go b/pkg/api/handlers/images_build.go deleted file mode 100644 index d969e3a47..000000000 --- a/pkg/api/handlers/images_build.go +++ /dev/null @@ -1,267 +0,0 @@ -package handlers - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containers/buildah" - "github.com/containers/buildah/imagebuildah" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/containers/storage/pkg/archive" -) - -func BuildImage(w http.ResponseWriter, r *http.Request) { - authConfigs := map[string]AuthConfig{} - if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { - authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) - if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { - utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) - return - } - } - - if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { - if hdr[0] != "application/x-tar" { - utils.BadRequest(w, "Content-Type", hdr[0], - fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) - } - } - - anchorDir, err := extractTarFile(r, w) - if err != nil { - utils.InternalServerError(w, err) - return - } - defer os.RemoveAll(anchorDir) - - query := struct { - Dockerfile string `schema:"dockerfile"` - Tag string `schema:"t"` - ExtraHosts string `schema:"extrahosts"` - Remote string `schema:"remote"` - Quiet bool `schema:"q"` - NoCache bool `schema:"nocache"` - CacheFrom string `schema:"cachefrom"` - Pull bool `schema:"pull"` - Rm bool `schema:"rm"` - ForceRm bool `schema:"forcerm"` - Memory int64 `schema:"memory"` - MemSwap int64 `schema:"memswap"` - CpuShares uint64 `schema:"cpushares"` - CpuSetCpus string `schema:"cpusetcpus"` - CpuPeriod uint64 `schema:"cpuperiod"` - CpuQuota int64 `schema:"cpuquota"` - BuildArgs string `schema:"buildargs"` - ShmSize int `schema:"shmsize"` - Squash bool `schema:"squash"` - Labels string `schema:"labels"` - NetworkMode string `schema:"networkmode"` - Platform string `schema:"platform"` - Target string `schema:"target"` - Outputs string `schema:"outputs"` - Registry string `schema:"registry"` - }{ - Dockerfile: "Dockerfile", - Tag: "", - ExtraHosts: "", - Remote: "", - Quiet: false, - NoCache: false, - CacheFrom: "", - Pull: false, - Rm: true, - ForceRm: false, - Memory: 0, - MemSwap: 0, - CpuShares: 0, - CpuSetCpus: "", - CpuPeriod: 0, - CpuQuota: 0, - BuildArgs: "", - ShmSize: 64 * 1024 * 1024, - Squash: false, - Labels: "", - NetworkMode: "", - Platform: "", - Target: "", - Outputs: "", - Registry: "docker.io", - } - - if err := decodeQuery(r, &query); err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) - return - } - - var ( - // Tag is the name with optional tag... - name = query.Tag - tag = "latest" - ) - if strings.Contains(query.Tag, ":") { - tokens := strings.SplitN(query.Tag, ":", 2) - name = tokens[0] - tag = tokens[1] - } - - if _, found := r.URL.Query()["target"]; found { - name = query.Target - } - - var buildArgs = map[string]string{} - if _, found := r.URL.Query()["buildargs"]; found { - if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { - utils.BadRequest(w, "buildargs", query.BuildArgs, err) - return - } - } - - // convert label formats - var labels = []string{} - if _, found := r.URL.Query()["labels"]; found { - var m = map[string]string{} - if err := json.Unmarshal([]byte(query.Labels), &m); err != nil { - utils.BadRequest(w, "labels", query.Labels, err) - return - } - - for k, v := range m { - labels = append(labels, k+"="+v) - } - } - - pullPolicy := buildah.PullIfMissing - if _, found := r.URL.Query()["pull"]; found { - if query.Pull { - pullPolicy = buildah.PullAlways - } - } - - // build events will be recorded here - var ( - buildEvents = []string{} - progress = bytes.Buffer{} - ) - - buildOptions := imagebuildah.BuildOptions{ - ContextDirectory: filepath.Join(anchorDir, "build"), - PullPolicy: pullPolicy, - Registry: query.Registry, - IgnoreUnrecognizedInstructions: true, - Quiet: query.Quiet, - Isolation: buildah.IsolationChroot, - Runtime: "", - RuntimeArgs: nil, - TransientMounts: nil, - Compression: archive.Gzip, - Args: buildArgs, - Output: name, - AdditionalTags: []string{tag}, - Log: func(format string, args ...interface{}) { - buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) - }, - In: nil, - Out: &progress, - Err: &progress, - SignaturePolicyPath: "", - ReportWriter: &progress, - OutputFormat: buildah.Dockerv2ImageManifest, - SystemContext: nil, - NamespaceOptions: nil, - ConfigureNetwork: 0, - CNIPluginPath: "", - CNIConfigDir: "", - IDMappingOptions: nil, - AddCapabilities: nil, - DropCapabilities: nil, - CommonBuildOpts: &buildah.CommonBuildOptions{ - AddHost: nil, - CgroupParent: "", - CPUPeriod: query.CpuPeriod, - CPUQuota: query.CpuQuota, - CPUShares: query.CpuShares, - CPUSetCPUs: query.CpuSetCpus, - CPUSetMems: "", - HTTPProxy: false, - Memory: query.Memory, - DNSSearch: nil, - DNSServers: nil, - DNSOptions: nil, - MemorySwap: query.MemSwap, - LabelOpts: nil, - SeccompProfilePath: "", - ApparmorProfile: "", - ShmSize: strconv.Itoa(query.ShmSize), - Ulimit: nil, - Volumes: nil, - }, - DefaultMountsFilePath: "", - IIDFile: "", - Squash: query.Squash, - Labels: labels, - Annotations: nil, - OnBuild: nil, - Layers: false, - NoCache: query.NoCache, - RemoveIntermediateCtrs: query.Rm, - ForceRmIntermediateCtrs: query.ForceRm, - BlobDirectory: "", - Target: query.Target, - Devices: nil, - } - - id, _, err := getRuntime(r).Build(r.Context(), buildOptions, query.Dockerfile) - if err != nil { - utils.InternalServerError(w, err) - } - - // Find image ID that was built... - utils.WriteResponse(w, http.StatusOK, - struct { - Stream string `json:"stream"` - }{ - Stream: progress.String() + "\n" + - strings.Join(buildEvents, "\n") + - fmt.Sprintf("\nSuccessfully built %s\n", id), - }) -} - -func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { - // build a home for the request body - anchorDir, err := ioutil.TempDir("", "libpod_builder") - if err != nil { - return "", err - } - buildDir := filepath.Join(anchorDir, "build") - - path := filepath.Join(anchorDir, "tarBall") - tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return "", err - } - defer tarBall.Close() - - // Content-Length not used as too many existing API clients didn't honor it - _, err = io.Copy(tarBall, r.Body) - r.Body.Close() - - if err != nil { - utils.InternalServerError(w, - fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) - } - - _, _ = tarBall.Seek(0, 0) - if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil { - return "", err - } - return anchorDir, nil -} diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index d8dd0d69b..8020c391d 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -10,17 +10,12 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -func StopContainer(w http.ResponseWriter, r *http.Request) { - handlers.StopContainer(w, r) -} - func ContainerExists(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) @@ -32,22 +27,6 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNoContent, "") } -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"` - }{ - // 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 - } - utils.RemoveContainer(w, r, query.Force, query.Vols) -} func ListContainers(w http.ResponseWriter, r *http.Request) { var ( filterFuncs []libpod.ContainerFilter @@ -165,16 +144,6 @@ func GetContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, data) } -func KillContainer(w http.ResponseWriter, r *http.Request) { - // /{version}/containers/(name)/kill - _, err := utils.KillContainer(w, r) - if err != nil { - return - } - // Success - utils.WriteResponse(w, http.StatusNoContent, "") -} - func WaitContainer(w http.ResponseWriter, r *http.Request) { exitCode, err := utils.WaitContainer(w, r) if err != nil { diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 71603e6cc..cfd3b993e 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -79,7 +79,7 @@ func ImageTree(w http.ResponseWriter, r *http.Request) { func GetImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) - newImage, err := handlers.GetImage(r, name) + newImage, err := utils.GetImage(r, name) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) return diff --git a/pkg/api/handlers/ping.go b/pkg/api/handlers/ping.go deleted file mode 100644 index d41da60f3..000000000 --- a/pkg/api/handlers/ping.go +++ /dev/null @@ -1,30 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/containers/buildah" -) - -// Ping returns headers to client about the service -// -// This handler must always be the same for the compatibility and libpod URL trees! -// Clients will use the Header availability to test which backend engine is in use. -func Ping(w http.ResponseWriter, r *http.Request) { - w.Header().Set("API-Version", DefaultApiVersion) - w.Header().Set("BuildKit-Version", "") - w.Header().Set("Docker-Experimental", "true") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Pragma", "no-cache") - - // API-Version and Libpod-API-Version may not always be equal - w.Header().Set("Libpod-API-Version", DefaultApiVersion) - w.Header().Set("Libpod-Buildha-Version", buildah.Version) - w.WriteHeader(http.StatusOK) - - if r.Method == http.MethodGet { - fmt.Fprint(w, "OK") - } - fmt.Fprint(w, "\n") -} diff --git a/pkg/api/handlers/unsupported.go b/pkg/api/handlers/unsupported.go deleted file mode 100644 index 956d31f8b..000000000 --- a/pkg/api/handlers/unsupported.go +++ /dev/null @@ -1,17 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/containers/libpod/pkg/api/handlers/utils" - log "github.com/sirupsen/logrus" -) - -func UnsupportedHandler(w http.ResponseWriter, r *http.Request) { - msg := fmt.Sprintf("Path %s is not supported", r.URL.Path) - log.Infof("Request Failed: %s", msg) - - utils.WriteJSON(w, http.StatusInternalServerError, - utils.ErrorModel{Message: msg}) -} diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index 07efef0f5..d5a79bdc8 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -2,9 +2,7 @@ package utils import ( "context" - "fmt" "net/http" - "syscall" "time" "github.com/containers/libpod/cmd/podman/shared" @@ -23,60 +21,6 @@ type ContainerCreateResponse struct { Warnings []string `json:"Warnings"` } -func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - query := struct { - Signal syscall.Signal `schema:"signal"` - }{ - Signal: syscall.SIGKILL, - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - return nil, err - } - name := GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - ContainerNotFound(w, name, err) - return nil, err - } - - state, err := con.State() - if err != nil { - InternalServerError(w, err) - return con, err - } - - // If the Container is stopped already, send a 409 - if state == define.ContainerStateStopped || state == define.ContainerStateExited { - Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name))) - return con, err - } - - err = con.Kill(uint(query.Signal)) - if err != nil { - Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) - } - return con, err -} - -func RemoveContainer(w http.ResponseWriter, r *http.Request, force, vols bool) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := GetName(r) - con, err := runtime.LookupContainer(name) - if err != nil { - ContainerNotFound(w, name, err) - return - } - - if err := runtime.RemoveContainer(r.Context(), con, force, vols); err != nil { - InternalServerError(w, err) - return - } - WriteResponse(w, http.StatusNoContent, "") -} - func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) { var ( err error diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index a97fd5c07..696d5f745 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -43,3 +43,8 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { } } + +func GetImage(r *http.Request, name string) (*image.Image, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + return runtime.ImageRuntime().NewFromLocal(name) +} diff --git a/pkg/api/handlers/version.go b/pkg/api/handlers/version.go deleted file mode 100644 index 94166952c..000000000 --- a/pkg/api/handlers/version.go +++ /dev/null @@ -1,73 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - goRuntime "runtime" - "time" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/utils" - docker "github.com/docker/docker/api/types" - "github.com/pkg/errors" -) - -const ( - DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/ - MinimalApiVersion = "1.24" -) - -func VersionHandler(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - versionInfo, err := define.GetVersion() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - - infoData, err := runtime.Info() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) - return - } - hostInfo := infoData[0].Data - - components := []docker.ComponentVersion{{ - Name: "Podman Engine", - Version: versionInfo.Version, - Details: map[string]string{ - "APIVersion": DefaultApiVersion, - "Arch": goRuntime.GOARCH, - "BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339), - "Experimental": "true", - "GitCommit": versionInfo.GitCommit, - "GoVersion": versionInfo.GoVersion, - "KernelVersion": hostInfo["kernel"].(string), - "MinAPIVersion": MinimalApiVersion, - "Os": goRuntime.GOOS, - }, - }} - - utils.WriteResponse(w, http.StatusOK, Version{Version: docker.Version{ - Platform: struct { - Name string - }{ - Name: fmt.Sprintf("%s/%s/%s", goRuntime.GOOS, goRuntime.GOARCH, hostInfo["Distribution"].(map[string]interface{})["distribution"].(string)), - }, - APIVersion: components[0].Details["APIVersion"], - Arch: components[0].Details["Arch"], - BuildTime: components[0].Details["BuildTime"], - Components: components, - Experimental: true, - GitCommit: components[0].Details["GitCommit"], - GoVersion: components[0].Details["GoVersion"], - KernelVersion: components[0].Details["KernelVersion"], - MinAPIVersion: components[0].Details["MinAPIVersion"], - Os: components[0].Details["Os"], - Version: components[0].Version, - }}) -} -- cgit v1.2.3-54-g00ecf