summaryrefslogtreecommitdiff
path: root/pkg/api
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-01-10 21:28:05 +0100
committerGitHub <noreply@github.com>2020-01-10 21:28:05 +0100
commite1ffac6cc73eb36640cbaf6a1a28ba44749a96d9 (patch)
tree61134dad4b2efdaa65dee6e7206e5ce20080302c /pkg/api
parent6ed88e047579bd2d1eac99a6089cc617f0c4773d (diff)
parent986feef2e80cfaed7cbce0df4fd5d619bcffefd7 (diff)
downloadpodman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.tar.gz
podman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.tar.bz2
podman-e1ffac6cc73eb36640cbaf6a1a28ba44749a96d9.zip
Merge pull request #4832 from baude/apiv2tomaster
Apiv2tomaster
Diffstat (limited to 'pkg/api')
-rw-r--r--pkg/api/handlers/containers.go194
-rw-r--r--pkg/api/handlers/containers_top.go61
-rw-r--r--pkg/api/handlers/events.go46
-rw-r--r--pkg/api/handlers/generic/containers.go306
-rw-r--r--pkg/api/handlers/generic/containers_create.go243
-rw-r--r--pkg/api/handlers/generic/containers_stats.go198
-rw-r--r--pkg/api/handlers/generic/images.go363
-rw-r--r--pkg/api/handlers/generic/info.go196
-rw-r--r--pkg/api/handlers/generic/ping.go25
-rw-r--r--pkg/api/handlers/generic/system.go18
-rw-r--r--pkg/api/handlers/generic/version.go74
-rw-r--r--pkg/api/handlers/handler.go46
-rw-r--r--pkg/api/handlers/images.go185
-rw-r--r--pkg/api/handlers/images_build.go233
-rw-r--r--pkg/api/handlers/libpod/containers.go186
-rw-r--r--pkg/api/handlers/libpod/healthcheck.go25
-rw-r--r--pkg/api/handlers/libpod/images.go165
-rw-r--r--pkg/api/handlers/libpod/pods.go440
-rw-r--r--pkg/api/handlers/libpod/volumes.go144
-rw-r--r--pkg/api/handlers/swagger.go126
-rw-r--r--pkg/api/handlers/types.go534
-rw-r--r--pkg/api/handlers/unsupported.go17
-rw-r--r--pkg/api/handlers/utils/containers.go103
-rw-r--r--pkg/api/handlers/utils/errors.go88
-rw-r--r--pkg/api/handlers/utils/handler.go44
-rw-r--r--pkg/api/handlers/utils/images.go32
-rw-r--r--pkg/api/server/handler_api.go37
-rw-r--r--pkg/api/server/register_auth.go11
-rw-r--r--pkg/api/server/register_containers.go729
-rw-r--r--pkg/api/server/register_distribution.go11
-rw-r--r--pkg/api/server/register_events.go32
-rw-r--r--pkg/api/server/register_healthcheck.go13
-rw-r--r--pkg/api/server/register_images.go571
-rw-r--r--pkg/api/server/register_info.go28
-rw-r--r--pkg/api/server/register_monitor.go11
-rw-r--r--pkg/api/server/register_ping.go17
-rw-r--r--pkg/api/server/register_plugins.go11
-rw-r--r--pkg/api/server/register_pods.go256
-rw-r--r--pkg/api/server/register_swarm.go27
-rw-r--r--pkg/api/server/register_system.go11
-rw-r--r--pkg/api/server/register_version.go12
-rw-r--r--pkg/api/server/register_volumes.go85
-rw-r--r--pkg/api/server/server.go189
-rw-r--r--pkg/api/server/swagger.go133
44 files changed, 6276 insertions, 0 deletions
diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go
new file mode 100644
index 000000000..6b09321a0
--- /dev/null
+++ b/pkg/api/handlers/containers.go
@@ -0,0 +1,194 @@
+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/gorilla/mux"
+ "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 := 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 302
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ utils.Error(w, http.StatusText(http.StatusNotModified), http.StatusNotModified,
+ errors.Errorf("Container %s is already stopped ", name))
+ 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 := 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.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 := 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 := 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 {
+ msg := fmt.Sprintf("Container %s is already running", name)
+ utils.Error(w, msg, http.StatusNotModified, errors.New(msg))
+ 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 := 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 := mux.Vars(r)["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/containers_top.go b/pkg/api/handlers/containers_top.go
new file mode 100644
index 000000000..bab559da1
--- /dev/null
+++ b/pkg/api/handlers/containers_top.go
@@ -0,0 +1,61 @@
+package handlers
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "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)
+
+ query := struct {
+ PsArgs string `schema:"ps_args"`
+ }{
+ PsArgs: "-ef",
+ }
+ 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 := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning {
+ utils.ContainerNotRunning(w, name, errors.Errorf("Container %s must be running to perform top operation", name))
+ return
+ }
+
+ output, err := ctnr.Top([]string{})
+ 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
new file mode 100644
index 000000000..900efa3da
--- /dev/null
+++ b/pkg/api/handlers/events.go
@@ -0,0 +1,46 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func GetEvents(w http.ResponseWriter, r *http.Request) {
+ query := struct {
+ Since string `json:"since"`
+ Until string `json:"until"`
+ Filters string `json:"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 filters = map[string][]string{}
+ if found := hasVar(r, "filters"); found {
+ if err := json.Unmarshal([]byte(query.Filters), &filters); err != nil {
+ utils.BadRequest(w, "filters", query.Filters, err)
+ return
+ }
+ }
+
+ var libpodFilters = make([]string, len(filters))
+ for k, v := range filters {
+ libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
+ }
+
+ libpodEvents, err := getRuntime(r).GetEvents(libpodFilters)
+ if err != nil {
+ utils.BadRequest(w, "filters", query.Filters, err)
+ return
+ }
+
+ var apiEvents = make([]*Event, len(libpodEvents))
+ for _, v := range libpodEvents {
+ apiEvents = append(apiEvents, EventToApiEvent(v))
+ }
+ utils.WriteJSON(w, http.StatusOK, apiEvents)
+}
diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/generic/containers.go
new file mode 100644
index 000000000..5a0a51fd7
--- /dev/null
+++ b/pkg/api/handlers/generic/containers.go
@@ -0,0 +1,306 @@
+package generic
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ Vols bool `schema:"v"`
+ Link bool `schema:"link"`
+ }{
+ // override any golang type defaults
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if query.Link {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ utils.ErrLinkNotSupport)
+ return
+ }
+ utils.RemoveContainer(w, r, query.Force, query.Vols)
+}
+
+func ListContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ infoData, err := runtime.Info()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain system info"))
+ return
+ }
+
+ var list = make([]*handlers.Container, len(containers))
+ for i, ctnr := range containers {
+ api, err := handlers.LibpodToContainer(ctnr, infoData)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ list[i] = api
+ }
+ utils.WriteResponse(w, http.StatusOK, list)
+}
+
+func GetContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ api, err := handlers.LibpodToContainerJSON(ctnr)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, api)
+}
+
+func KillContainer(w http.ResponseWriter, r *http.Request) {
+ // /{version}/containers/(name)/kill
+ con, err := utils.KillContainer(w, r)
+ if err != nil {
+ return
+ }
+ // the kill behavior for docker differs from podman in that they appear to wait
+ // for the Container to croak so the exit code is accurate immediately after the
+ // kill is sent. libpod does not. but we can add a wait here only for the docker
+ // side of things and mimic that behavior
+ if _, err = con.Wait(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func WaitContainer(w http.ResponseWriter, r *http.Request) {
+ var msg string
+ // /{version}/containers/(name)/wait
+ exitCode, err := utils.WaitContainer(w, r)
+ if err != nil {
+ msg = err.Error()
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
+ StatusCode: int(exitCode),
+ Error: struct {
+ Message string
+ }{
+ Message: msg,
+ },
+ })
+}
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ containers, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ deletedContainers := []string{}
+ var spaceReclaimed uint64
+ for _, ctnr := range containers {
+ // Only remove stopped or exit'ed containers.
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ switch state {
+ case define.ContainerStateStopped, define.ContainerStateExited:
+ default:
+ continue
+ }
+
+ deletedContainers = append(deletedContainers, ctnr.ID())
+ cSize, err := ctnr.RootFsSize()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ spaceReclaimed += uint64(cSize)
+
+ err = runtime.RemoveContainer(context.Background(), ctnr, false, false)
+ if err != nil && !(errors.Cause(err) == define.ErrCtrRemoved) {
+ utils.InternalServerError(w, err)
+ return
+ }
+ }
+ report := types.ContainersPruneReport{
+ ContainersDeleted: deletedContainers,
+ SpaceReclaimed: spaceReclaimed,
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Follow bool `schema:"follow"`
+ Stdout bool `schema:"stdout"`
+ Stderr bool `schema:"stderr"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Timestamps bool `schema:"timestamps"`
+ Tail string `schema:"tail"`
+ }{
+ Tail: "all",
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if !(query.Stdout || query.Stderr) {
+ msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
+ utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
+ return
+ }
+
+ name := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ var tail int64 = -1
+ if query.Tail != "all" {
+ tail, err = strconv.ParseInt(query.Tail, 0, 64)
+ if err != nil {
+ utils.BadRequest(w, "tail", query.Tail, err)
+ return
+ }
+ }
+
+ var since time.Time
+ if _, found := mux.Vars(r)["since"]; found {
+ since, err = util.ParseInputTime(query.Since)
+ if err != nil {
+ utils.BadRequest(w, "since", query.Since, err)
+ return
+ }
+ }
+
+ var until time.Time
+ if _, found := mux.Vars(r)["until"]; found {
+ since, err = util.ParseInputTime(query.Until)
+ if err != nil {
+ utils.BadRequest(w, "until", query.Until, err)
+ return
+ }
+ }
+
+ options := &logs.LogOptions{
+ Details: true,
+ Follow: query.Follow,
+ Since: since,
+ Tail: tail,
+ Timestamps: query.Timestamps,
+ }
+
+ var wg sync.WaitGroup
+ options.WaitGroup = &wg
+
+ logChannel := make(chan *logs.LogLine, tail+1)
+ if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ go func() {
+ wg.Wait()
+ close(logChannel)
+ }()
+
+ w.WriteHeader(http.StatusOK)
+ var builder strings.Builder
+ for ok := true; ok; ok = query.Follow {
+ for line := range logChannel {
+ if _, found := mux.Vars(r)["until"]; found {
+ if line.Time.After(until) {
+ break
+ }
+ }
+
+ // Reset variables we're ready to loop again
+ builder.Reset()
+ header := [8]byte{}
+
+ switch line.Device {
+ case "stdout":
+ if !query.Stdout {
+ continue
+ }
+ header[0] = 1
+ case "stderr":
+ if !query.Stderr {
+ continue
+ }
+ header[0] = 2
+ default:
+ // Logging and moving on is the best we can do here. We may have already sent
+ // a Status and Content-Type to client therefore we can no longer report an error.
+ log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ continue
+ }
+
+ if query.Timestamps {
+ builder.WriteString(line.Time.Format(time.RFC3339))
+ builder.WriteRune(' ')
+ }
+ builder.WriteString(line.Msg)
+
+ // Build header and output entry
+ binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len()))
+ if _, err := w.Write(header[:]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := fmt.Fprint(w, builder.String()); err != nil {
+ log.Errorf("unable to write builder string: %q", err)
+ }
+
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+ }
+}
diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go
new file mode 100644
index 000000000..ef5337abd
--- /dev/null
+++ b/pkg/api/handlers/generic/containers_create.go
@@ -0,0 +1,243 @@
+package generic
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "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"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/storage"
+ "github.com/docker/docker/pkg/signal"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+ "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
+ }
+
+ 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
+ var pod *libpod.Pod
+ ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod)
+ if err != nil {
+ if strings.Contains(err.Error(), "invalid log driver") {
+ // this does not quite work yet and needs a little more massaging
+ w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
+ w.WriteHeader(http.StatusInternalServerError)
+ msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)
+ if _, err := fmt.Fprintln(w, msg); err != nil {
+ log.Errorf("%s: %q", msg, err)
+ }
+ //s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type))
+ return
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
+ return
+ }
+
+ type ctrCreateResponse struct {
+ Id string `json:"Id"`
+ Warnings []string `json:"Warnings"`
+ }
+ response := ctrCreateResponse{
+ Id: ctr.ID(),
+ Warnings: []string{}}
+
+ utils.WriteResponse(w, http.StatusCreated, response)
+}
+
+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
new file mode 100644
index 000000000..0c4efc1df
--- /dev/null
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -0,0 +1,198 @@
+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"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/cgroups"
+ docker "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "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) {
+ // 200 no error
+ // 404 no such
+ // 500 internal
+ 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 := mux.Vars(r)["name"]
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := ctnr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning && !query.Stream {
+ utils.WriteJSON(w, http.StatusOK, &handlers.Stats{StatsJSON: docker.StatsJSON{
+ Name: ctnr.Name(),
+ ID: ctnr.ID(),
+ }})
+ return
+ }
+
+ var preRead time.Time
+ var preCPUStats docker.CPUStats
+
+ stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
+ return
+ }
+
+ if query.Stream {
+ preRead = time.Now()
+ preCPUStats = docker.CPUStats{
+ CPUUsage: docker.CPUUsage{
+ TotalUsage: stats.CPUNano,
+ PercpuUsage: []uint64{uint64(stats.CPU)},
+ UsageInKernelmode: 0,
+ UsageInUsermode: 0,
+ },
+ SystemUsage: 0,
+ OnlineCPUs: 0,
+ ThrottlingData: docker.ThrottlingData{},
+ }
+ time.Sleep(DefaultStatsPeriod)
+ }
+
+ cgroupPath, _ := ctnr.CGroupPath()
+ cgroup, _ := cgroups.Load(cgroupPath)
+
+ for ok := true; ok; ok = query.Stream {
+ state, _ := ctnr.State()
+ if state != define.ContainerStateRunning {
+ time.Sleep(10 * time.Second)
+ continue
+ }
+
+ stats, _ := ctnr.GetContainerStats(stats)
+ cgroupStat, _ := cgroup.Stat()
+ inspect, _ := ctnr.Inspect(false)
+
+ net := make(map[string]docker.NetworkStats)
+ net[inspect.NetworkSettings.EndpointID] = 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: "",
+ }
+
+ s := handlers.Stats{StatsJSON: docker.StatsJSON{
+ Stats: docker.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,
+ },
+ NumProcs: 0,
+ StorageStats: docker.StorageStats{
+ ReadCountNormalized: 0,
+ ReadSizeBytes: 0,
+ WriteCountNormalized: 0,
+ WriteSizeBytes: 0,
+ },
+ CPUStats: docker.CPUStats{
+ CPUUsage: docker.CPUUsage{
+ TotalUsage: cgroupStat.CPU.Usage.Total,
+ PercpuUsage: []uint64{uint64(stats.CPU)},
+ UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
+ UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
+ },
+ SystemUsage: 0,
+ 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)
+ }
+ time.Sleep(DefaultStatsPeriod)
+ }
+}
+
+func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry {
+ results := make([]docker.BlkioStatEntry, 0, 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
new file mode 100644
index 000000000..8029ee861
--- /dev/null
+++ b/pkg/api/handlers/generic/images.go
@@ -0,0 +1,363 @@
+package generic
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+ "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/containers/storage"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func ExportImage(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 server
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ 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) {
+ // 200 no error
+ // 500 internal
+ var (
+ dangling bool = true
+ err error
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ filters map[string]string
+ }{
+ // 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
+ }
+
+ // FIXME This is likely wrong due to it not being a map[string][]string
+
+ // until ts is not supported on podman prune
+ if len(query.filters["until"]) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "until is not supported yet"))
+ return
+ }
+ // labels are not supported on podman prune
+ if len(query.filters["label"]) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet"))
+ return
+ }
+
+ if len(query.filters["dangling"]) > 0 {
+ dangling, err = strconv.ParseBool(query.filters["dangling"])
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
+ return
+ }
+ }
+ idr := []types.ImageDeleteResponseItem{}
+ //
+ // This code needs to be migrated to libpod to work correctly. I could not
+ // work my around the information docker needs with the existing prune in libpod.
+ //
+ pruneImages, err := runtime.ImageRuntime().GetPruneImages(!dangling, []image2.ImageFilter{})
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get images to prune"))
+ return
+ }
+ for _, p := range pruneImages {
+ repotags, err := p.RepoTags()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get repotags for image"))
+ return
+ }
+ if err := p.Remove(r.Context(), true); err != nil {
+ if errors.Cause(err) == storage.ErrImageUsedByContainer {
+ logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
+ continue
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to prune image"))
+ return
+ }
+ // newimageevent is not export therefore we cannot record the event. this will be fixed
+ // when the prune is fixed in libpod
+ // defer p.newImageEvent(events.Prune)
+ response := types.ImageDeleteResponseItem{
+ Deleted: fmt.Sprintf("sha256:%s", p.ID()), // I ack this is not ideal
+ }
+ if len(repotags) > 0 {
+ response.Untagged = repotags[0]
+ }
+ idr = append(idr, response)
+ }
+ 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
+ changes string
+ comment string
+ container string
+ //fromSrc string # fromSrc is currently unused
+ pause bool
+ repo string
+ tag string
+ }{
+ // 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
+ changes []string
+ }{
+ // 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
+ tag string
+ }{
+ // 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 := mux.Vars(r)["name"]
+ 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) {
+ // 200 ok
+ // 500 internal
+ 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)
+}
diff --git a/pkg/api/handlers/generic/info.go b/pkg/api/handlers/generic/info.go
new file mode 100644
index 000000000..c9e79233d
--- /dev/null
+++ b/pkg/api/handlers/generic/info.go
@@ -0,0 +1,196 @@
+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/ping.go b/pkg/api/handlers/generic/ping.go
new file mode 100644
index 000000000..44a67d53f
--- /dev/null
+++ b/pkg/api/handlers/generic/ping.go
@@ -0,0 +1,25 @@
+package generic
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func PingGET(w http.ResponseWriter, _ *http.Request) {
+ setHeaders(w)
+ fmt.Fprintln(w, "OK")
+}
+
+func PingHEAD(w http.ResponseWriter, _ *http.Request) {
+ setHeaders(w)
+ fmt.Fprintln(w, "")
+}
+
+func setHeaders(w http.ResponseWriter) {
+ 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")
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/pkg/api/handlers/generic/system.go b/pkg/api/handlers/generic/system.go
new file mode 100644
index 000000000..edf1f8522
--- /dev/null
+++ b/pkg/api/handlers/generic/system.go
@@ -0,0 +1,18 @@
+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/version.go b/pkg/api/handlers/generic/version.go
new file mode 100644
index 000000000..39423914d
--- /dev/null
+++ b/pkg/api/handlers/generic/version.go
@@ -0,0 +1,74 @@
+package generic
+
+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"
+)
+
+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, 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/handler.go b/pkg/api/handlers/handler.go
new file mode 100644
index 000000000..2efeb1379
--- /dev/null
+++ b/pkg/api/handlers/handler.go
@@ -0,0 +1,46 @@
+package handlers
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// Convenience routines to reduce boiler plate in handlers
+
+func getVar(r *http.Request, k string) string {
+ return mux.Vars(r)[k]
+}
+
+func hasVar(r *http.Request, k string) bool {
+ _, found := mux.Vars(r)[k]
+ return found
+}
+func getName(r *http.Request) string {
+ return getVar(r, "name")
+}
+
+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
new file mode 100644
index 000000000..d4cddbfb2
--- /dev/null
+++ b/pkg/api/handlers/images.go
@@ -0,0 +1,185 @@
+package handlers
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func HistoryImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ 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 := mux.Vars(r)["name"]
+ 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) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ name := mux.Vars(r)["name"]
+ 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 LoadImage(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ //quiet bool # quiet is currently unused
+ }{
+ // 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, "")
+ 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),
+ })
+}
+
+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
+ }
+ // TODO filters are a bit undefined here in terms of what exactly the input looks
+ // like. We need to understand that a bit more.
+ options := image.SearchOptions{
+ Filter: image.SearchFilter{},
+ Limit: query.Limit,
+ }
+ results, err := image.SearchImages(query.Term, options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, results)
+}
diff --git a/pkg/api/handlers/images_build.go b/pkg/api/handlers/images_build.go
new file mode 100644
index 000000000..c7c746392
--- /dev/null
+++ b/pkg/api/handlers/images_build.go
@@ -0,0 +1,233 @@
+package handlers
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah"
+ "github.com/containers/buildah/imagebuildah"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/storage/pkg/archive"
+ log "github.com/sirupsen/logrus"
+)
+
+func BuildImage(w http.ResponseWriter, r *http.Request) {
+ authConfigs := map[string]AuthConfig{}
+ if hasHeader(r, "X-Registry-Config") {
+ registryHeader := getHeader(r, "X-Registry-Config")
+ authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(registryHeader))
+ if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil {
+ utils.BadRequest(w, "X-Registry-Config", registryHeader, json.NewDecoder(authConfigsJSON).Decode(&authConfigs))
+ return
+ }
+ }
+
+ anchorDir, err := extractTarFile(r, w)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // defer os.RemoveAll(anchorDir)
+
+ query := struct {
+ Dockerfile string `json:"dockerfile"`
+ Tag string `json:"t"`
+ ExtraHosts string `json:"extrahosts"`
+ Remote string `json:"remote"`
+ Quiet bool `json:"q"`
+ NoCache bool `json:"nocache"`
+ CacheFrom string `json:"cachefrom"`
+ Pull string `json:"pull"`
+ Rm bool `json:"rm"`
+ ForceRm bool `json:"forcerm"`
+ Memory int `json:"memory"`
+ MemSwap int `json:"memswap"`
+ CpuShares int `json:"cpushares"`
+ CpuSetCpus string `json:"cpusetcpus"`
+ CpuPeriod int `json:"cpuperiod"`
+ CpuQuota int `json:"cpuquota"`
+ BuildArgs string `json:"buildargs"`
+ ShmSize int `json:"shmsize"`
+ Squash bool `json:"squash"`
+ Labels string `json:"labels"`
+ NetworkMode string `json:"networkmode"`
+ Platform string `json:"platform"`
+ Target string `json:"target"`
+ Outputs string `json:"outputs"`
+ }{
+ Dockerfile: "Dockerfile",
+ Tag: "",
+ ExtraHosts: "",
+ Remote: "",
+ Quiet: false,
+ NoCache: false,
+ CacheFrom: "",
+ Pull: "",
+ 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: "",
+ }
+
+ if err := decodeQuery(r, &query); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ return
+ }
+
+ // Tag is the name with optional tag...
+ var name = query.Tag
+ var tag string
+ if strings.Contains(query.Tag, ":") {
+ tokens := strings.SplitN(query.Tag, ":", 2)
+ name = tokens[0]
+ tag = tokens[1]
+ }
+
+ var buildArgs = map[string]string{}
+ if found := hasVar(r, "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 hasVar(r, "labels") {
+ 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, fmt.Sprintf("%s=%v", k, v))
+ }
+ }
+
+ buildOptions := imagebuildah.BuildOptions{
+ ContextDirectory: filepath.Join(anchorDir, "build"),
+ PullPolicy: 0,
+ Registry: "",
+ IgnoreUnrecognizedInstructions: false,
+ Quiet: query.Quiet,
+ Isolation: 0,
+ Runtime: "",
+ RuntimeArgs: nil,
+ TransientMounts: nil,
+ Compression: 0,
+ Args: buildArgs,
+ Output: name,
+ AdditionalTags: []string{tag},
+ Log: nil,
+ In: nil,
+ Out: nil,
+ Err: nil,
+ SignaturePolicyPath: "",
+ ReportWriter: nil,
+ OutputFormat: "",
+ SystemContext: nil,
+ NamespaceOptions: nil,
+ ConfigureNetwork: 0,
+ CNIPluginPath: "",
+ CNIConfigDir: "",
+ IDMappingOptions: nil,
+ AddCapabilities: nil,
+ DropCapabilities: nil,
+ CommonBuildOpts: &buildah.CommonBuildOptions{},
+ 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: fmt.Sprintf("Successfully built %s\n", id),
+ })
+}
+
+func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) {
+ var (
+ // length int64
+ // n int64
+ copyErr 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()
+
+ // if hasHeader(r, "Content-Length") {
+ // length, err := strconv.ParseInt(getHeader(r, "Content-Length"), 10, 64)
+ // if err != nil {
+ // return "", errors.New(fmt.Sprintf("Failed request: unable to parse Content-Length of '%s'", getHeader(r, "Content-Length")))
+ // }
+ // n, copyErr = io.CopyN(tarBall, r.Body, length+1)
+ // } else {
+ _, copyErr = io.Copy(tarBall, r.Body)
+ // }
+ r.Body.Close()
+
+ if copyErr != nil {
+ utils.InternalServerError(w,
+ fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI))
+ }
+ log.Debugf("Content-Length: %s", getVar(r, "Content-Length"))
+
+ // if hasHeader(r, "Content-Length") && n != length {
+ // return "", errors.New(fmt.Sprintf("Failed request: Given Content-Length does not match file size %d != %d", n, length))
+ // }
+
+ _, _ = 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
new file mode 100644
index 000000000..bfb028b1b
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers.go
@@ -0,0 +1,186 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func StopContainer(w http.ResponseWriter, r *http.Request) {
+ handlers.StopContainer(w, r)
+}
+
+func ContainerExists(w http.ResponseWriter, r *http.Request) {
+ // 404 no such container
+ // 200 ok
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ _, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func RemoveContainer(w http.ResponseWriter, r *http.Request) {
+ // 204 no error
+ // 400 bad param
+ // 404 no such container
+ // 409 conflict
+ // 500 internal error
+ 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) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Filter []string `schema:"filter"`
+ Last int `schema:"last"`
+ Size bool `schema:"size"`
+ Sync bool `schema:"sync"`
+ }{
+ // 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
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ opts := shared.PsOptions{
+ All: true,
+ Last: query.Last,
+ Size: query.Size,
+ Sort: "",
+ Namespace: true,
+ Sync: query.Sync,
+ }
+
+ pss, err := shared.GetPsContainerOutput(runtime, opts, query.Filter, 2)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, pss)
+}
+
+func GetContainer(w http.ResponseWriter, r *http.Request) {
+ 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, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ container, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ data, err := container.Inspect(query.Size)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ 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) {
+ _, err := utils.WaitContainer(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ // TODO Needs rebase to get filers; Also would be handy to define
+ // an actual libpod container prune method.
+ // force
+ // filters
+}
+
+func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
+ // follow
+ // since
+ // timestamps
+ // tail string
+}
+func StatsContainer(w http.ResponseWriter, r *http.Request) {
+ //stream
+}
+func CreateContainer(w http.ResponseWriter, r *http.Request) {
+
+}
+
+func MountContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ conn, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ m, err := conn.Mount()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, m)
+}
+
+func ShowMountedContainers(w http.ResponseWriter, r *http.Request) {
+ response := make(map[string]string)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ conns, err := runtime.GetAllContainers()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ for _, conn := range conns {
+ mounted, mountPoint, err := conn.Mounted()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ if !mounted {
+ continue
+ }
+ response[conn.ID()] = mountPoint
+ }
+ utils.WriteResponse(w, http.StatusOK, response)
+}
diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go
new file mode 100644
index 000000000..0d7bf3ea7
--- /dev/null
+++ b/pkg/api/handlers/libpod/healthcheck.go
@@ -0,0 +1,25 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+)
+
+func RunHealthCheck(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ status, err := runtime.HealthCheck(name)
+ if err != nil {
+ if status == libpod.HealthCheckContainerNotFound {
+ utils.ContainerNotFound(w, name, err)
+ }
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, status)
+}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
new file mode 100644
index 000000000..0d4e220a8
--- /dev/null
+++ b/pkg/api/handlers/libpod/images.go
@@ -0,0 +1,165 @@
+package libpod
+
+import (
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// Commit
+// author string
+// "container"
+// repo string
+// tag string
+// message
+// pause bool
+// changes []string
+
+// create
+
+func ImageExists(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+
+ _, 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
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func ImageTree(w http.ResponseWriter, r *http.Request) {
+ // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework
+
+ //name := mux.Vars(r)["name"]
+ //_, layerInfoMap, _, err := s.Runtime.Tree(name)
+ //if err != nil {
+ // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name))
+ // return
+ //}
+ // it is not clear to me how to deal with this given all the processing of the image
+ // is in main. we need to discuss how that really should be and return something useful.
+ handlers.UnsupportedHandler(w, r)
+}
+
+func GetImage(w http.ResponseWriter, r *http.Request) {
+ name := mux.Vars(r)["name"]
+ 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 := newImage.Inspect(r.Context())
+ if err != nil {
+ utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %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
+ }
+ // libpod has additional fields that we need to populate.
+ is.CreatedTime = img.Created()
+ is.ReadOnly = img.IsReadOnly()
+ summaries[j] = is
+ }
+ utils.WriteResponse(w, http.StatusOK, summaries)
+}
+
+func PruneImages(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ All bool `schema:"all"`
+ Filters []string `schema:"filters"`
+ }{
+ // 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
+ }
+ cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, query.Filters)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, cids)
+}
+
+func ExportImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Compress bool `schema:"compress"`
+ Format string `schema:"format"`
+ }{
+ // 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 len(query.Format) < 1 {
+ utils.InternalServerError(w, errors.New("format parameter cannot be empty."))
+ 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
+ }
+ name := mux.Vars(r)["name"]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ 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)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
new file mode 100644
index 000000000..daaf9d018
--- /dev/null
+++ b/pkg/api/handlers/libpod/pods.go
@@ -0,0 +1,440 @@
+package libpod
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/cmd/podman/shared/parse"
+ "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/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PodCreate(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ options []libpod.PodCreateOption
+ err error
+ )
+ labels := make(map[string]string)
+ input := handlers.PodCreateConfig{}
+ 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.InfraCommand) > 0 || len(input.InfraImage) > 0 {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError,
+ errors.New("infra-command and infra-image are not implemented yet"))
+ return
+ }
+ // TODO long term we should break the following out of adapter and into libpod proper
+ // so that the cli and api can share the creation of a pod with the same options
+ if len(input.CGroupParent) > 0 {
+ options = append(options, libpod.WithPodCgroupParent(input.CGroupParent))
+ }
+
+ if len(input.Labels) > 0 {
+ if err := parse.ReadKVStrings(labels, []string{}, input.Labels); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ }
+
+ if len(labels) != 0 {
+ options = append(options, libpod.WithPodLabels(labels))
+ }
+
+ if len(input.Name) > 0 {
+ options = append(options, libpod.WithPodName(input.Name))
+ }
+
+ if len(input.Hostname) > 0 {
+ options = append(options, libpod.WithPodHostname(input.Hostname))
+ }
+
+ if input.Infra {
+ // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix
+ // when implemented in libpod
+ options = append(options, libpod.WithInfraContainer())
+ sharedNamespaces := shared.DefaultKernelNamespaces
+ if len(input.Share) > 0 {
+ sharedNamespaces = input.Share
+ }
+ nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ","))
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ options = append(options, nsOptions...)
+ }
+
+ if len(input.Publish) > 0 {
+ portBindings, err := shared.CreatePortBindings(input.Publish)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ options = append(options, libpod.WithInfraContainerPorts(portBindings))
+
+ }
+ // always have containers use pod cgroups
+ // User Opt out is not yet supported
+ options = append(options, libpod.WithPodCgroups())
+
+ pod, err := runtime.NewPod(r.Context(), options...)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
+}
+
+func Pods(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ podInspectData []*libpod.PodInspect
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ filters []string `schema:"filters"`
+ }{
+ // 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 len(query.filters) > 0 {
+ utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
+ return
+ }
+ pods, err := runtime.GetAllPods()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ for _, pod := range pods {
+ data, err := pod.Inspect()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ podInspectData = append(podInspectData, data)
+ }
+ utils.WriteResponse(w, http.StatusOK, podInspectData)
+}
+
+func PodInspect(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ podData, err := pod.Inspect()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, podData)
+}
+
+func PodStop(w http.ResponseWriter, r *http.Request) {
+ // 200
+ // 304 not modified
+ // 404 no such
+ // 500 internal
+ var (
+ stopError error
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ 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
+ }
+ allContainersStopped := true
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+
+ // TODO we need to implement a pod.State/Status in libpod internal so libpod api
+ // users dont have to run through all containers.
+ podContainers, err := pod.AllContainers()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ for _, con := range podContainers {
+ containerState, err := con.State()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ if containerState == define.ContainerStateRunning {
+ allContainersStopped = false
+ break
+ }
+ }
+ if allContainersStopped {
+ alreadyStopped := errors.Errorf("pod %s is already stopped", pod.ID())
+ utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyStopped)
+ return
+ }
+
+ if query.timeout > 0 {
+ _, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout)
+ } else {
+ _, stopError = pod.Stop(r.Context(), false)
+ }
+ if stopError != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodStart(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ allContainersRunning := true
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+
+ // TODO we need to implement a pod.State/Status in libpod internal so libpod api
+ // users dont have to run through all containers.
+ podContainers, err := pod.AllContainers()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+
+ for _, con := range podContainers {
+ containerState, err := con.State()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ if containerState != define.ContainerStateRunning {
+ allContainersRunning = false
+ break
+ }
+ }
+ if allContainersRunning {
+ alreadyRunning := errors.Errorf("pod %s is already running", pod.ID())
+ utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyRunning)
+ return
+ }
+ if _, err := pod.Start(r.Context()); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodDelete(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ force bool `schema:"force"`
+ }{
+ // 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 := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ if err := runtime.RemovePod(r.Context(), pod, true, query.force); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodRestart(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Restart(r.Context())
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodPrune(w http.ResponseWriter, r *http.Request) {
+ var (
+ err error
+ pods []*libpod.Pod
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ force bool `schema:"force"`
+ }{
+ // 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.force {
+ pods, err = runtime.GetAllPods()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ } else {
+ // TODO We need to make a libpod.PruneVolumes or this code will be a mess. Volumes
+ // already does this right. It will also help clean this code path up with less
+ // conditionals. We do this when we integrate with libpod again.
+ utils.Error(w, "not implemented", http.StatusInternalServerError, errors.New("not implemented"))
+ return
+ }
+ for _, p := range pods {
+ if err := runtime.RemovePod(r.Context(), p, true, query.force); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodPause(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Pause()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
+
+func PodUnpause(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ _, err = pod.Unpause()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodKill(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ signal int `schema:"signal"`
+ }{
+ // 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 := mux.Vars(r)["name"]
+ pod, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ podStates, err := pod.Status()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ hasRunning := false
+ for _, s := range podStates {
+ if s == define.ContainerStateRunning {
+ hasRunning = true
+ break
+ }
+ }
+ if !hasRunning {
+ msg := fmt.Sprintf("Container %s is not running", pod.ID())
+ utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID()))
+ return
+ }
+ // TODO How do we differentiate if a signal was sent vs accepting the pod/container default?
+ _, err = pod.Kill(uint(query.signal))
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func PodExists(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := mux.Vars(r)["name"]
+ _, err := runtime.LookupPod(name)
+ if err != nil {
+ utils.PodNotFound(w, name, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
new file mode 100644
index 000000000..3e0e597c6
--- /dev/null
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -0,0 +1,144 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+func CreateVolume(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 500 internal
+ var (
+ volumeOptions []libpod.VolumeCreateOption
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ }{
+ // override any golang type defaults
+ }
+ input := handlers.VolumeCreateConfig{}
+ 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
+ }
+
+ // decode params from body
+ 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.Name) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name))
+ }
+ if len(input.Driver) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver))
+ }
+ if len(input.Label) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label))
+ }
+ if len(input.Opts) > 0 {
+ parsedOptions, err := shared.ParseVolumeOptions(input.Opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ volumeOptions = append(volumeOptions, parsedOptions...)
+ }
+ vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, vol.Name())
+}
+
+func InspectVolume(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ name := mux.Vars(r)["name"]
+ vol, err := runtime.GetVolume(name)
+ if err != nil {
+ utils.VolumeNotFound(w, name, err)
+ }
+ inspect, err := vol.Inspect()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, inspect)
+}
+
+func ListVolumes(w http.ResponseWriter, r *http.Request) {
+ //var (
+ // runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ // decoder = r.Context().Value("decoder").(*schema.Decoder)
+ //)
+ //query := struct {
+ // Filter string `json:"filter"`
+ //}{
+ // // 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
+ //}
+ /*
+ This is all in main in cmd and needs to be extracted from there first.
+ */
+
+}
+
+func PruneVolumes(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ pruned, errs := runtime.PruneVolumes(r.Context())
+ if errs != nil {
+ if len(errs) > 1 {
+ for _, err := range errs {
+ log.Infof("Request Failed(%s): %s", http.StatusText(http.StatusInternalServerError), err.Error())
+ }
+ }
+ utils.InternalServerError(w, errs[len(errs)-1])
+ }
+ utils.WriteResponse(w, http.StatusOK, pruned)
+}
+
+func RemoveVolume(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ // 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 := mux.Vars(r)["name"]
+ vol, err := runtime.LookupVolume(name)
+ if err != nil {
+ utils.VolumeNotFound(w, name, err)
+ }
+ if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go
new file mode 100644
index 000000000..b677a5a0b
--- /dev/null
+++ b/pkg/api/handlers/swagger.go
@@ -0,0 +1,126 @@
+package handlers
+
+import (
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/inspect"
+ "github.com/docker/docker/api/types"
+)
+
+// History response
+// swagger:response DocsHistory
+type swagHistory struct {
+ // in:body
+ Body struct {
+ HistoryResponse
+ }
+}
+
+// Inspect response
+// swagger:response DocsImageInspect
+type swagImageInspect struct {
+ // in:body
+ Body struct {
+ ImageInspect
+ }
+}
+
+// Delete response
+// swagger:response DocsImageDeleteResponse
+type swagImageDeleteResponse struct {
+ // in:body
+ Body struct {
+ image.ImageDeleteResponse
+ }
+}
+
+// Search results
+// swagger:response DocsSearchResponse
+type swagSearchResponse struct {
+ // in:body
+ Body struct {
+ image.SearchResult
+ }
+}
+
+// Inspect image
+// swagger:response DocsLibpodInspectImageResponse
+type swagLibpodInspectImageResponse struct {
+ // in:body
+ Body struct {
+ inspect.ImageData
+ }
+}
+
+// Prune containers
+// swagger:response DocsContainerPruneReport
+type swagContainerPruneReport struct {
+ // in: body
+ Body struct {
+ ContainersPruneReport
+ }
+}
+
+// Inspect container
+// swagger:response DocsContainerInspectResponse
+type swagContainerInspectResponse struct {
+ // in:body
+ Body struct {
+ types.ContainerJSON
+ }
+}
+
+// List processes in container
+// swagger:response DockerTopResponse
+type swagDockerTopResponse struct {
+ // in:body
+ Body struct {
+ ContainerTopOKBody
+ }
+}
+
+// List containers
+// swagger:response LibpodListContainersResponse
+type swagLibpodListContainersResponse struct {
+ // in:body
+ Body struct {
+ shared.PsContainerOutput
+ }
+}
+
+// Inspect container
+// swagger:response LibpodInspectContainerResponse
+type swagLibpodInspectContainerResponse struct {
+ // in:body
+ Body struct {
+ libpod.InspectContainerData
+ }
+}
+
+// List pods
+// swagger:response ListPodsResponse
+type swagListPodsResponse struct {
+ // in:body
+ Body struct {
+ libpod.PodInspect
+ }
+}
+
+// Inspect pod
+// swagger:response InspectPodResponse
+type swagInspectPodResponse struct {
+ // in:body
+ Body struct {
+ libpod.PodInspect
+ }
+}
+
+// Inspect volume
+// swagger:response InspectVolumeResponse
+type swagInspectVolumeResponse struct {
+ // in:body
+ Body struct {
+ libpod.InspectVolumeData
+ }
+}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
new file mode 100644
index 000000000..9edbbdccc
--- /dev/null
+++ b/pkg/api/handlers/types.go
@@ -0,0 +1,534 @@
+package handlers
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/events"
+ libpodImage "github.com/containers/libpod/libpod/image"
+ docker "github.com/docker/docker/api/types"
+ dockerContainer "github.com/docker/docker/api/types/container"
+ dockerEvents "github.com/docker/docker/api/types/events"
+ dockerNetwork "github.com/docker/docker/api/types/network"
+ "github.com/docker/go-connections/nat"
+ "github.com/pkg/errors"
+)
+
+type AuthConfig struct {
+ docker.AuthConfig
+}
+
+type ImageInspect struct {
+ docker.ImageInspect
+}
+
+type ContainerConfig struct {
+ dockerContainer.Config
+}
+
+type ImageSummary struct {
+ docker.ImageSummary
+ CreatedTime time.Time `json:"CreatedTime,omitempty"`
+ ReadOnly bool `json:"ReadOnly,omitempty"`
+}
+
+type ContainersPruneReport struct {
+ docker.ContainersPruneReport
+}
+
+type Info struct {
+ docker.Info
+ BuildahVersion string
+ CPURealtimePeriod bool
+ CPURealtimeRuntime bool
+ CgroupVersion string
+ Rootless bool
+ SwapFree int64
+ SwapTotal int64
+ Uptime string
+}
+
+type Container struct {
+ docker.Container
+ docker.ContainerCreateConfig
+}
+
+type ContainerStats struct {
+ docker.ContainerStats
+}
+
+type Ping struct {
+ docker.Ping
+}
+
+type Version struct {
+ docker.Version
+}
+
+type DiskUsage struct {
+ docker.DiskUsage
+}
+
+type VolumesPruneReport struct {
+ docker.VolumesPruneReport
+}
+
+type ImagesPruneReport struct {
+ docker.ImagesPruneReport
+}
+
+type BuildCachePruneReport struct {
+ docker.BuildCachePruneReport
+}
+
+type NetworkPruneReport struct {
+ docker.NetworksPruneReport
+}
+
+type ConfigCreateResponse struct {
+ docker.ConfigCreateResponse
+}
+
+type PushResult struct {
+ docker.PushResult
+}
+
+type BuildResult struct {
+ docker.BuildResult
+}
+
+type ContainerWaitOKBody struct {
+ StatusCode int
+ Error struct {
+ Message string
+ }
+}
+
+type CreateContainerConfig struct {
+ Name string
+ dockerContainer.Config
+ HostConfig dockerContainer.HostConfig
+ NetworkingConfig dockerNetwork.NetworkingConfig
+}
+
+type VolumeCreateConfig struct {
+ Name string `json:"name"`
+ Driver string `schema:"driver"`
+ Label map[string]string `schema:"label"`
+ Opts map[string]string `schema:"opts"`
+}
+
+type IDResponse struct {
+ ID string `json:"id"`
+}
+
+type Stats struct {
+ docker.StatsJSON
+}
+
+type ContainerTopOKBody struct {
+ dockerContainer.ContainerTopOKBody
+ ID string `json:"Id"`
+}
+
+type PodCreateConfig struct {
+ Name string `json:"name"`
+ CGroupParent string `json:"cgroup-parent"`
+ Hostname string `json:"hostname"`
+ Infra bool `json:"infra"`
+ InfraCommand string `json:"infra-command"`
+ InfraImage string `json:"infra-image"`
+ Labels []string `json:"labels"`
+ Publish []string `json:"publish"`
+ Share string `json:"share"`
+}
+
+type ErrorModel struct {
+ Message string `json:"message"`
+}
+
+type Event struct {
+ dockerEvents.Message
+}
+
+type HistoryResponse struct {
+ ID string `json:"Id"`
+ Created int64 `json:"Created"`
+ CreatedBy string `json:"CreatedBy"`
+ Tags []string `json:"Tags"`
+ Size int64 `json:"Size"`
+ Comment string `json:"Comment"`
+}
+
+type ImageLayer struct{}
+
+type ImageTreeResponse struct {
+ ID string `json:"id"`
+ Tags []string `json:"tags"`
+ Size string `json:"size"`
+ Layers []ImageLayer `json:"layers"`
+}
+
+func EventToApiEvent(e *events.Event) *Event {
+ return &Event{dockerEvents.Message{
+ Type: e.Type.String(),
+ Action: e.Status.String(),
+ Actor: dockerEvents.Actor{
+ ID: e.ID,
+ Attributes: map[string]string{
+ "image": e.Image,
+ "name": e.Name,
+ "containerExitCode": strconv.Itoa(e.ContainerExitCode),
+ },
+ },
+ Scope: "local",
+ Time: e.Time.Unix(),
+ TimeNano: e.Time.UnixNano(),
+ }}
+}
+
+func ImageToImageSummary(l *libpodImage.Image) (*ImageSummary, error) {
+ containers, err := l.Containers()
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Containers for image %s", l.ID())
+ }
+ containerCount := len(containers)
+
+ var digests []string
+ for _, d := range l.Digests() {
+ digests = append(digests, string(d))
+ }
+
+ tags, err := l.RepoTags()
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain RepoTags for image %s", l.ID())
+ }
+
+ // FIXME: GetParent() panics
+ // parent, err := l.GetParent(context.TODO())
+ // if err != nil {
+ // return nil, errors.Wrapf(err, "Failed to obtain ParentID for image %s", l.ID())
+ // }
+
+ labels, err := l.Labels(context.TODO())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Labels for image %s", l.ID())
+ }
+
+ size, err := l.Size(context.TODO())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain Size for image %s", l.ID())
+ }
+ dockerSummary := docker.ImageSummary{
+ Containers: int64(containerCount),
+ Created: l.Created().Unix(),
+ ID: l.ID(),
+ Labels: labels,
+ ParentID: l.Parent,
+ RepoDigests: digests,
+ RepoTags: tags,
+ SharedSize: 0,
+ Size: int64(*size),
+ VirtualSize: int64(*size),
+ }
+ is := ImageSummary{
+ ImageSummary: dockerSummary,
+ }
+ return &is, nil
+}
+
+func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageInspect, error) {
+ info, err := l.Inspect(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ ports, err := portsToPortSet(info.Config.ExposedPorts)
+ if err != nil {
+ return nil, err
+ }
+ // TODO the rest of these still need wiring!
+ config := dockerContainer.Config{
+ // Hostname: "",
+ // Domainname: "",
+ User: info.User,
+ // AttachStdin: false,
+ // AttachStdout: false,
+ // AttachStderr: false,
+ ExposedPorts: ports,
+ // Tty: false,
+ // OpenStdin: false,
+ // StdinOnce: false,
+ Env: info.Config.Env,
+ Cmd: info.Config.Cmd,
+ // Healthcheck: nil,
+ // ArgsEscaped: false,
+ // Image: "",
+ // Volumes: nil,
+ // WorkingDir: "",
+ // Entrypoint: nil,
+ // NetworkDisabled: false,
+ // MacAddress: "",
+ // OnBuild: nil,
+ // Labels: nil,
+ // StopSignal: "",
+ // StopTimeout: nil,
+ // Shell: nil,
+ }
+ ic, err := l.ToImageRef(ctx)
+ if err != nil {
+ return nil, err
+ }
+ dockerImageInspect := docker.ImageInspect{
+ Architecture: l.Architecture,
+ Author: l.Author,
+ Comment: info.Comment,
+ Config: &config,
+ Created: l.Created().Format(time.RFC3339Nano),
+ DockerVersion: "",
+ GraphDriver: docker.GraphDriverData{},
+ ID: fmt.Sprintf("sha256:%s", l.ID()),
+ Metadata: docker.ImageMetadata{},
+ Os: l.Os,
+ OsVersion: l.Version,
+ Parent: l.Parent,
+ RepoDigests: info.RepoDigests,
+ RepoTags: info.RepoTags,
+ RootFS: docker.RootFS{},
+ Size: info.Size,
+ Variant: "",
+ VirtualSize: info.VirtualSize,
+ }
+ bi := ic.ConfigInfo()
+ // For docker images, we need to get the Container id and config
+ // and populate the image with it.
+ if bi.MediaType == manifest.DockerV2Schema2ConfigMediaType {
+ d := manifest.Schema2Image{}
+ b, err := ic.ConfigBlob(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(b, &d); err != nil {
+ return nil, err
+ }
+ // populate the Container id into the image
+ dockerImageInspect.Container = d.Container
+ containerConfig := dockerContainer.Config{}
+ configBytes, err := json.Marshal(d.ContainerConfig)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(configBytes, &containerConfig); err != nil {
+ return nil, err
+ }
+ // populate the Container config in the image
+ dockerImageInspect.ContainerConfig = &containerConfig
+ // populate parent
+ dockerImageInspect.Parent = d.Parent.String()
+ }
+ return &ImageInspect{dockerImageInspect}, nil
+
+}
+
+func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Container, error) {
+ imageId, imageName := l.Image()
+ sizeRW, err := l.RWSize()
+ if err != nil {
+ return nil, err
+ }
+
+ SizeRootFs, err := l.RootFsSize()
+ if err != nil {
+ return nil, err
+ }
+
+ state, err := l.State()
+ if err != nil {
+ return nil, err
+ }
+
+ return &Container{docker.Container{
+ ID: l.ID(),
+ Names: []string{l.Name()},
+ Image: imageName,
+ ImageID: imageId,
+ Command: strings.Join(l.Command(), " "),
+ Created: l.CreatedTime().Unix(),
+ Ports: nil,
+ SizeRw: sizeRW,
+ SizeRootFs: SizeRootFs,
+ Labels: l.Labels(),
+ State: string(state),
+ Status: "",
+ HostConfig: struct {
+ NetworkMode string `json:",omitempty"`
+ }{
+ "host"},
+ NetworkSettings: nil,
+ Mounts: nil,
+ },
+ docker.ContainerCreateConfig{},
+ }, nil
+}
+
+func LibpodToContainerJSON(l *libpod.Container) (*docker.ContainerJSON, error) {
+ _, imageName := l.Image()
+ inspect, err := l.Inspect(true)
+ if err != nil {
+ return nil, err
+ }
+ i, err := json.Marshal(inspect.State)
+ if err != nil {
+ return nil, err
+ }
+ state := docker.ContainerState{}
+ if err := json.Unmarshal(i, &state); err != nil {
+ return nil, err
+ }
+
+ // docker considers paused to be running
+ if state.Paused {
+ state.Running = true
+ }
+
+ h, err := json.Marshal(inspect.HostConfig)
+ if err != nil {
+ return nil, err
+ }
+ hc := dockerContainer.HostConfig{}
+ if err := json.Unmarshal(h, &hc); err != nil {
+ return nil, err
+ }
+ g, err := json.Marshal(inspect.GraphDriver)
+ if err != nil {
+ return nil, err
+ }
+ graphDriver := docker.GraphDriverData{}
+ if err := json.Unmarshal(g, &graphDriver); err != nil {
+ return nil, err
+ }
+
+ cb := docker.ContainerJSONBase{
+ ID: l.ID(),
+ Created: l.CreatedTime().String(),
+ Path: "",
+ Args: nil,
+ State: &state,
+ Image: imageName,
+ ResolvConfPath: inspect.ResolvConfPath,
+ HostnamePath: inspect.HostnamePath,
+ HostsPath: inspect.HostsPath,
+ LogPath: l.LogPath(),
+ Node: nil,
+ Name: l.Name(),
+ RestartCount: 0,
+ Driver: inspect.Driver,
+ Platform: "linux",
+ MountLabel: inspect.MountLabel,
+ ProcessLabel: inspect.ProcessLabel,
+ AppArmorProfile: inspect.AppArmorProfile,
+ ExecIDs: inspect.ExecIDs,
+ HostConfig: &hc,
+ GraphDriver: graphDriver,
+ SizeRw: inspect.SizeRw,
+ SizeRootFs: &inspect.SizeRootFs,
+ }
+
+ stopTimeout := int(l.StopTimeout())
+
+ ports := make(nat.PortSet)
+ for p := range inspect.HostConfig.PortBindings {
+ splitp := strings.Split(p, "/")
+ port, err := nat.NewPort(splitp[0], splitp[1])
+ if err != nil {
+ return nil, err
+ }
+ ports[port] = struct{}{}
+ }
+
+ config := dockerContainer.Config{
+ Hostname: l.Hostname(),
+ Domainname: inspect.Config.DomainName,
+ User: l.User(),
+ AttachStdin: inspect.Config.AttachStdin,
+ AttachStdout: inspect.Config.AttachStdout,
+ AttachStderr: inspect.Config.AttachStderr,
+ ExposedPorts: ports,
+ Tty: inspect.Config.Tty,
+ OpenStdin: inspect.Config.OpenStdin,
+ StdinOnce: inspect.Config.StdinOnce,
+ Env: inspect.Config.Env,
+ Cmd: inspect.Config.Cmd,
+ Healthcheck: nil,
+ ArgsEscaped: false,
+ Image: imageName,
+ Volumes: nil,
+ WorkingDir: l.WorkingDir(),
+ Entrypoint: l.Entrypoint(),
+ NetworkDisabled: false,
+ MacAddress: "",
+ OnBuild: nil,
+ Labels: l.Labels(),
+ StopSignal: string(l.StopSignal()),
+ StopTimeout: &stopTimeout,
+ Shell: nil,
+ }
+
+ m, err := json.Marshal(inspect.Mounts)
+ if err != nil {
+ return nil, err
+ }
+ mounts := []docker.MountPoint{}
+ if err := json.Unmarshal(m, &mounts); err != nil {
+ return nil, err
+ }
+
+ networkSettingsDefault := docker.DefaultNetworkSettings{
+ EndpointID: "",
+ Gateway: "",
+ GlobalIPv6Address: "",
+ GlobalIPv6PrefixLen: 0,
+ IPAddress: "",
+ IPPrefixLen: 0,
+ IPv6Gateway: "",
+ MacAddress: l.Config().StaticMAC.String(),
+ }
+
+ networkSettings := docker.NetworkSettings{
+ NetworkSettingsBase: docker.NetworkSettingsBase{},
+ DefaultNetworkSettings: networkSettingsDefault,
+ Networks: nil,
+ }
+
+ c := docker.ContainerJSON{
+ ContainerJSONBase: &cb,
+ Mounts: mounts,
+ Config: &config,
+ NetworkSettings: &networkSettings,
+ }
+ return &c, nil
+}
+
+// portsToPortSet converts libpods exposed ports to dockers structs
+func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) {
+ ports := make(nat.PortSet)
+ for k := range input {
+ npTCP, err := nat.NewPort("tcp", k)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to create tcp port from %s", k)
+ }
+ npUDP, err := nat.NewPort("udp", k)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to create udp port from %s", k)
+ }
+ ports[npTCP] = struct{}{}
+ ports[npUDP] = struct{}{}
+ }
+ return ports, nil
+}
diff --git a/pkg/api/handlers/unsupported.go b/pkg/api/handlers/unsupported.go
new file mode 100644
index 000000000..956d31f8b
--- /dev/null
+++ b/pkg/api/handlers/unsupported.go
@@ -0,0 +1,17 @@
+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
new file mode 100644
index 000000000..64d3d378a
--- /dev/null
+++ b/pkg/api/handlers/utils/containers.go
@@ -0,0 +1,103 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+ "syscall"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decorder").(*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 := mux.Vars(r)["name"]
+ 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 := mux.Vars(r)["name"]
+ 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) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ // /{version}/containers/(name)/restart
+ query := struct {
+ Interval string `schema:"interval"`
+ Condition string `schema:"condition"`
+ }{
+ // Override golang default values for types
+ }
+ 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 0, err
+ }
+
+ if len(query.Condition) > 0 {
+ return 0, errors.Errorf("the condition parameter is not supported")
+ }
+
+ name := mux.Vars(r)["name"]
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ ContainerNotFound(w, name, err)
+ return 0, err
+ }
+ if len(query.Interval) > 0 {
+ d, err := time.ParseDuration(query.Interval)
+ if err != nil {
+ Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %s for interval", query.Interval))
+ }
+ return con.WaitWithInterval(d)
+ }
+ return con.Wait()
+}
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
new file mode 100644
index 000000000..3ec0742bd
--- /dev/null
+++ b/pkg/api/handlers/utils/errors.go
@@ -0,0 +1,88 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+var (
+ ErrLinkNotSupport = errors.New("Link is not supported")
+)
+
+// Error formats an API response to an error
+//
+// apiMessage and code must match the container API, and are sent to client
+// err is logged on the system running the podman service
+func Error(w http.ResponseWriter, apiMessage string, code int, err error) {
+ // Log detailed message of what happened to machine running podman service
+ log.Infof("Request Failed(%s): %s", http.StatusText(code), err.Error())
+ em := ErrorModel{
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ }
+ WriteJSON(w, code, em)
+}
+
+func VolumeNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchVolume {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such volume: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+func ContainerNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchCtr {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such container: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func ImageNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchImage {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such image: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func PodNotFound(w http.ResponseWriter, nameOrId string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchPod {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such pod: %s", nameOrId)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
+func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
+ msg := fmt.Sprintf("Container %s is not running", containerID)
+ Error(w, msg, http.StatusConflict, err)
+}
+
+func InternalServerError(w http.ResponseWriter, err error) {
+ Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, err)
+}
+
+func BadRequest(w http.ResponseWriter, key string, value string, err error) {
+ e := errors.Wrapf(err, "Failed to parse query parameter '%s': %q", key, value)
+ Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, e)
+}
+
+type ErrorModel struct {
+ // root cause
+ Because string `json:"cause"`
+ // error message
+ Message string `json:"message"`
+}
+
+func (e ErrorModel) Error() string {
+ return e.Message
+}
+
+func (e ErrorModel) Cause() error {
+ return errors.New(e.Because)
+}
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
new file mode 100644
index 000000000..0815e6eca
--- /dev/null
+++ b/pkg/api/handlers/utils/handler.go
@@ -0,0 +1,44 @@
+package utils
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// WriteResponse encodes the given value as JSON or string and renders it for http client
+func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
+ switch value.(type) {
+ case string:
+ w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
+ w.WriteHeader(code)
+
+ if _, err := fmt.Fprintln(w, value); err != nil {
+ log.Errorf("unable to send string response: %q", err)
+ }
+ case *os.File:
+ w.Header().Set("Content-Type", "application/octet; charset=us-ascii")
+ w.WriteHeader(code)
+
+ if _, err := io.Copy(w, value.(*os.File)); err != nil {
+ log.Errorf("unable to copy to response: %q", err)
+ }
+ default:
+ WriteJSON(w, code, value)
+ }
+}
+
+func WriteJSON(w http.ResponseWriter, code int, value interface{}) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+ if err := coder.Encode(value); err != nil {
+ log.Errorf("unable to write json: %q", err)
+ }
+}
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
new file mode 100644
index 000000000..9445298ca
--- /dev/null
+++ b/pkg/api/handlers/utils/images.go
@@ -0,0 +1,32 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/gorilla/schema"
+)
+
+// GetImages is a common function used to get images for libpod and other compatibility
+// mechanisms
+func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ query := struct {
+ //all bool # all is currently unused
+ filters []string
+ //digests bool # digests is currently unused
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ return nil, err
+ }
+ filters := query.filters
+ if len(filters) < 1 {
+ filters = append(filters, fmt.Sprintf("reference=%s", ""))
+ }
+ return runtime.ImageRuntime().GetImagesWithFilters(filters)
+}
diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go
new file mode 100644
index 000000000..4b93998ee
--- /dev/null
+++ b/pkg/api/server/handler_api.go
@@ -0,0 +1,37 @@
+package server
+
+import (
+ "context"
+ "net/http"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// APIHandler is a wrapper to enhance HandlerFunc's and remove redundant code
+func APIHandler(ctx context.Context, h http.HandlerFunc) http.HandlerFunc {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Debugf("APIHandler -- Method: %s URL: %s", r.Method, r.URL.String())
+ if err := r.ParseForm(); err != nil {
+ log.Infof("Failed Request: unable to parse form: %q", err)
+ }
+
+ // TODO: Use ConnContext when ported to go 1.13
+ c := context.WithValue(r.Context(), "decoder", ctx.Value("decoder"))
+ c = context.WithValue(c, "runtime", ctx.Value("runtime"))
+ c = context.WithValue(c, "shutdownFunc", ctx.Value("shutdownFunc"))
+ r = r.WithContext(c)
+
+ h(w, r)
+
+ shutdownFunc := r.Context().Value("shutdownFunc").(func() error)
+ if err := shutdownFunc(); err != nil {
+ log.Errorf("Failed to shutdown Server in APIHandler(): %s", err.Error())
+ }
+ })
+}
+
+// VersionedPath prepends the version parsing code
+// any handler may override this default when registering URL(s)
+func VersionedPath(p string) string {
+ return "/v{version:[0-9][0-9.]*}" + p
+}
diff --git a/pkg/api/server/register_auth.go b/pkg/api/server/register_auth.go
new file mode 100644
index 000000000..9f312683d
--- /dev/null
+++ b/pkg/api/server/register_auth.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterAuthHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/auth"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
new file mode 100644
index 000000000..af1881624
--- /dev/null
+++ b/pkg/api/server/register_containers.go
@@ -0,0 +1,729 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error {
+ // swagger:operation POST /containers/create containers createContainer
+ //
+ // Create a container
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: name
+ // type: string
+ // description: container name
+ // responses:
+ // '201':
+ // description: tbd
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/types/ConflictError"
+ // '500':
+ // "$ref": "#/types/InternalError"
+ r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/json containers listContainers
+ //
+ // List containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // "$ref": "#/responses/Container"
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/json"), APIHandler(s.Context, generic.ListContainers)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/prune containers pruneContainers
+ //
+ // Prune unused containers
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: something
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/DocsContainerPruneReport"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost)
+ // swagger:operation DELETE /containers/{nameOrID} containers removeContainer
+ //
+ // Delete container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: force
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: v
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: link
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}"), APIHandler(s.Context, generic.RemoveContainer)).Methods(http.MethodDelete)
+ // swagger:operation GET /containers/{nameOrID}/json containers getContainer
+ //
+ // Inspect Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/DocsContainerInspectResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/json"), APIHandler(s.Context, generic.GetContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/kill containers killContainer
+ //
+ // Kill Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: signal
+ // type: int
+ // description: signal to be sent to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/kill"), APIHandler(s.Context, generic.KillContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/logs containers LogsFromContainer
+ //
+ // Get logs from container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: follow
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: stdout
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: stderr
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: since
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: until
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: timestamps
+ // type: bool
+ // description: needs description
+ // - in: query
+ // name: tail
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: tbd
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/pause containers pauseContainer
+ //
+ // Pause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/rename"), APIHandler(s.Context, handlers.UnsupportedHandler)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/restart containers restartContainer
+ //
+ // Restart Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout before sending kill signal to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/start containers startContainer
+ //
+ // Start a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: detachKeys
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/stats containers statsContainer
+ //
+ // Get stats for a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: stream
+ // type: bool
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: tbd
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/stop containers stopContainer
+ //
+ // Stop a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: number of seconds to wait before killing container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStoppedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /containers/{nameOrID}/top containers topContainer
+ //
+ // List processes running inside a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: ps_args
+ // type: string
+ // description: arguments to pass to ps such as aux
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "ref": "#/responses/DockerTopResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /containers/{nameOrID}/unpause containers unpauseContainer
+ //
+ // Unpause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // schema:
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/{nameOrID}/wait containers waitContainer
+ //
+ // Wait on a container to exit
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: condition
+ // type: string
+ // description: Wait until the container reaches the given condition
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name:..*}/wait"), APIHandler(s.Context, generic.WaitContainer)).Methods(http.MethodPost)
+
+ /*
+ libpod endpoints
+ */
+
+ r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, libpod.CreateContainer)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/containers/json containers listContainers
+ //
+ // List containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // "$ref": "#/responses/LibpodListContainersResponse"
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/prune containers pruneContainers
+ //
+ // Prune unused containers
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: something
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: something
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, libpod.PruneContainers)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/containers/showmounted containers showMounterContainers
+ //
+ // Show mounted containers
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: mounted containers
+ // schema:
+ // type: object
+ // additionalProperties:
+ // type: string
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), APIHandler(s.Context, libpod.ShowMountedContainers)).Methods(http.MethodGet)
+ // swagger:operation DELETE /libpod/containers/json containers removeContainer
+ //
+ // Delete container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: force
+ // type: bool
+ // description: need something
+ // - in: query
+ // name: v
+ // type: bool
+ // description: need something
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}"), APIHandler(s.Context, libpod.RemoveContainer)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/containers/{nameOrID}/json containers getContainer
+ //
+ // Inspect Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: size
+ // type: bool
+ // description: display filesystem usage
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/LibpodInspectContainerResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/json"), APIHandler(s.Context, libpod.GetContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/kill containers killContainer
+ //
+ // Kill Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: signal
+ // type: int
+ // default: 15
+ // description: signal to be sent to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '409':
+ // "$ref": "#/responses/ConflictError"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/kill"), APIHandler(s.Context, libpod.KillContainer)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/containers/{nameOrID}/mount containers mountContainer
+ //
+ // Mount a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: mounted container
+ // schema:
+ // description: id
+ // type: string
+ // example: 3c784de79b791b4ebd3ac55e511f97fedc042328499554937a3f8bfd9c1a2cb8
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/mount"), APIHandler(s.Context, libpod.MountContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/logs"), APIHandler(s.Context, libpod.LogsFromContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/pause containers pauseContainer
+ //
+ // Pause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/restart containers restartContainer
+ //
+ // Restart Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout before sending kill signal to container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/start containers startContainer
+ //
+ // Start a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: detachKeys
+ // type: string
+ // description: needs description
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStartedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stats"), APIHandler(s.Context, libpod.StatsContainer)).Methods(http.MethodGet)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/unpause containers unpauseContainer
+ //
+ // Unpause Container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/wait containers waitContainer
+ //
+ // Wait on a container to exit
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: condition
+ // type: string
+ // description: Wait until the container reaches the given condition
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/wait"), APIHandler(s.Context, libpod.WaitContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/containers/{nameOrID}/exists containers containerExists
+ //
+ // Check if container exists
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: container exists
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/exists"), APIHandler(s.Context, libpod.ContainerExists)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/containers/{nameOrID}/stop containers stopContainer
+ //
+ // Stop a container
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: t
+ // type: int
+ // description: number of seconds to wait before killing container
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // "$ref": "#/responses/ContainerAlreadyStoppedError"
+ // '404':
+ // "$ref": "#/responses/NoSuchContainer"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_distribution.go b/pkg/api/server/register_distribution.go
new file mode 100644
index 000000000..23820b4a7
--- /dev/null
+++ b/pkg/api/server/register_distribution.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterDistributionHandlers(r *mux.Router) error {
+ r.HandleFunc(VersionedPath("/distribution/{name:..*}/json"), handlers.UnsupportedHandler)
+ return nil
+}
diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go
new file mode 100644
index 000000000..d764fdbb4
--- /dev/null
+++ b/pkg/api/server/register_events.go
@@ -0,0 +1,32 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error {
+ // swagger:operation GET /events system getEvents
+ // ---
+ // summary: Returns events filtered on query parameters
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: since
+ // in: query
+ // description: start streaming events from this time
+ // - name: until
+ // in: query
+ // description: stop streaming events later than this
+ // - name: filters
+ // in: query
+ // description: JSON encoded map[string][]string of constraints
+ // responses:
+ // "200":
+ // description: OK
+ // "500":
+ // description: Failed
+ // "$ref": "#/types/errorModel"
+ r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents))
+ return nil
+}
diff --git a/pkg/api/server/register_healthcheck.go b/pkg/api/server/register_healthcheck.go
new file mode 100644
index 000000000..e4cc145d5
--- /dev/null
+++ b/pkg/api/server/register_healthcheck.go
@@ -0,0 +1,13 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/libpod/containers/{name:..*}/runhealthcheck"), APIHandler(s.Context, libpod.RunHealthCheck)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
new file mode 100644
index 000000000..488427f3c
--- /dev/null
+++ b/pkg/api/server/register_images.go
@@ -0,0 +1,571 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
+ // swagger:operation POST /images/create images createImage
+ //
+ // Create an image from an image
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromImage
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: tag
+ // type: string
+ // description: needs description
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "TBD"
+ // '404':
+ // description: repo or image does not exist
+ // schema:
+ // $ref: "#/responses/generalError"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // $ref: '#/responses/GenericError'
+ r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}")
+ // swagger:operation POST /images/create images createImage
+ //
+ // Create an image from Source
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromSrc
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: changes
+ // type: TBD
+ // description: needs description
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "TBD"
+ // '404':
+ // description: repo or image does not exist
+ // schema:
+ // $ref: "#/responses/generalError"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // $ref: '#/responses/GenericError'
+ r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}")
+ // swagger:operation GET /images/json images listImages
+ //
+ // List Images
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // type: array
+ // items:
+ // schema:
+ // $ref: "#/responses/DockerImageSummary"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet)
+ // swagger:operation POST /images/load images loadImage
+ //
+ // Import image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: quiet
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ // swagger:operation POST /images/prune images pruneImages
+ //
+ // Prune unused images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost)
+ // swagger:operation GET /images/search images searchImages
+ //
+ // Search images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: term
+ // type: string
+ // description: term to search
+ // - in: query
+ // name: limit
+ // type: int
+ // description: maximum number of results
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: TBD
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsSearchResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet)
+ // swagger:operation DELETE /images/{nameOrID} images removeImage
+ //
+ // Remove Image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: remove the image even if used by containers or has other tags
+ // - in: query
+ // name: noprune
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '404':
+ // $ref: '#/responses/BadParamError'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ // swagger:operation GET /images/{nameOrID}/get images exportImage
+ //
+ // Export an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // $ref: "TBD"
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/{nameOrID}/history images imageHistory
+ //
+ // History of an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsHistory"
+ // '404':
+ // $ref: "#/responses/NoSuchImage"
+ // '500':
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/images/{name:..*}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/{nameOrID}/json images inspectImage
+ //
+ // Inspect an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // $ref: "#/responses/DocsImageInspect"
+ // '404':
+ // $ref: "#/responses/NoSuchImage"
+ // '500':
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage))
+ // swagger:operation POST /images/{nameOrID}/tag images tagImage
+ //
+ // Tag an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository to tag in
+ // - in: query
+ // name: tag
+ // type: string
+ // description: the name of the new tag
+ // produces:
+ // - application/json
+ // responses:
+ // 201:
+ // description: no error
+ // 400:
+ // $ref: '#/responses/BadParamError'
+ // 404:
+ // $ref: '#/responses/NoSuchImage'
+ // 409:
+ // $ref: '#/responses/ConflictError'
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost)
+ // swagger:operation POST /commit/ commit commitContainer
+ //
+ // Create a new image from a container
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: container
+ // type: string
+ // description: the name or ID of a container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository name for the created image
+ // - in: query
+ // name: tag
+ // type: string
+ // description: tag name for the created image
+ // - in: query
+ // name: comment
+ // type: string
+ // description: commit message
+ // - in: query
+ // name: author
+ // type: string
+ // description: author of the image
+ // - in: query
+ // name: pause
+ // type: bool
+ // description: pause the container before committing it
+ // - in: query
+ // name: changes
+ // type: string
+ // description: instructions to apply while committing in Dockerfile format
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // description: no error
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/commit"), APIHandler(s.Context, generic.CommitContainer)).Methods(http.MethodPost)
+
+ /*
+ libpod endpoints
+ */
+
+ // swagger:operation POST /libpod/images/{nameOrID}/exists images imageExists
+ //
+ // Check if image exists in local store
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: fromImage
+ // type: string
+ // description: needs description
+ // - in: query
+ // name: tag
+ // type: string
+ // description: needs description
+ // responses:
+ // '204':
+ // description: image exists
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/exists"), APIHandler(s.Context, libpod.ImageExists))
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/tree"), APIHandler(s.Context, libpod.ImageTree))
+ // swagger:operation GET /libpod/images/{nameOrID}/history images imageHistory
+ //
+ // History of an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/HistoryResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/images/json images listImages
+ //
+ // List Images
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DockerImageSummary"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/images/load images loadImage
+ //
+ // Import image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: quiet
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/images/prune images pruneImages
+ //
+ // Prune unused images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: image filters
+ // - in: query
+ // name: all
+ // type: bool
+ // description: prune all images
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/prune"), APIHandler(s.Context, libpod.PruneImages)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/images/search images searchImages
+ //
+ // Search images
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: term
+ // type: string
+ // description: term to search
+ // - in: query
+ // name: limit
+ // type: int
+ // description: maximum number of results
+ // - in: query
+ // name: filters
+ // type: map[string][]string
+ // description: TBD
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsSearchResponse"
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet)
+ // swagger:operation DELETE /libpod/images/{nameOrID} images removeImage
+ //
+ // Remove Image
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // type: bool
+ // description: remove the image even if used by containers or has other tags
+ // - in: query
+ // name: noprune
+ // type: bool
+ // description: not supported
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsIageDeleteResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/images/{nameOrID}/get images exportImage
+ //
+ // Export an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: format
+ // type: string
+ // description: format for exported image
+ // - in: query
+ // name: compress
+ // type: bool
+ // description: use compression on image
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: TBD
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/get"), APIHandler(s.Context, libpod.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/images/{nameOrID}/json images inspectImage
+ //
+ // Inspect an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // items:
+ // $ref: "#/responses/DocsLibpodInspectImageResponse"
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/json"), APIHandler(s.Context, libpod.GetImage))
+ // swagger:operation POST /libpod/images/{nameOrID}/tag images tagImage
+ //
+ // Tag an image
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: repo
+ // type: string
+ // description: the repository to tag in
+ // - in: query
+ // name: tag
+ // type: string
+ // description: the name of the new tag
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // description: no error
+ // '400':
+ // $ref: '#/responses/BadParamError'
+ // '404':
+ // $ref: '#/responses/NoSuchImage'
+ // '409':
+ // $ref: '#/responses/ConflictError'
+ // '500':
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost)
+
+ r.Handle(VersionedPath("/build"), APIHandler(s.Context, handlers.BuildImage)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go
new file mode 100644
index 000000000..797158553
--- /dev/null
+++ b/pkg/api/server/register_info.go
@@ -0,0 +1,28 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerInfoHandlers(r *mux.Router) error {
+ // swagger:operation GET /info libpod getInfo
+ //
+ // Returns information on the system and libpod configuration
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // schema:
+ // "$ref": "#/types/Info"
+ // '500':
+ // description: unexpected error
+ // schema:
+ // "$ref": "#/types/ErrorModel"
+ r.Handle(VersionedPath("/info"), APIHandler(s.Context, generic.GetInfo)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_monitor.go b/pkg/api/server/register_monitor.go
new file mode 100644
index 000000000..e6c235419
--- /dev/null
+++ b/pkg/api/server/register_monitor.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterMonitorHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/monitor"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_ping.go b/pkg/api/server/register_ping.go
new file mode 100644
index 000000000..4956f9822
--- /dev/null
+++ b/pkg/api/server/register_ping.go
@@ -0,0 +1,17 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPingHandlers(r *mux.Router) error {
+ r.Handle("/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet)
+ r.Handle("/_ping", APIHandler(s.Context, generic.PingHEAD)).Methods("HEAD")
+
+ // libpod
+ r.Handle("/libpod/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_plugins.go b/pkg/api/server/register_plugins.go
new file mode 100644
index 000000000..7fd6b9c4c
--- /dev/null
+++ b/pkg/api/server/register_plugins.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) RegisterPluginsHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/plugins"), APIHandler(s.Context, handlers.UnsupportedHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
new file mode 100644
index 000000000..c2e10277e
--- /dev/null
+++ b/pkg/api/server/register_pods.go
@@ -0,0 +1,256 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
+ // swagger:operation GET /libpod/pods/json pods ListPods
+ //
+ // List Pods
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: filters
+ // descriptions: needs description and plumbing for filters
+ // responses:
+ // '200':
+ // $ref: "#/responses/ListPodsResponse"
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/json"), APIHandler(s.Context, libpod.Pods)).Methods(http.MethodGet)
+ r.Handle(VersionedPath("/libpod/pods/create"), APIHandler(s.Context, libpod.PodCreate)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/prune pods PrunePods
+ //
+ // Prune unused pods
+ //
+ // ---
+ // parameters:
+ // - in: query
+ // name: force
+ // description: force delete
+ // type: bool
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost)
+ // swagger:operation DELETE /libpod/pods/{nameOrID} pods removePod
+ //
+ // Remove Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: force
+ // type: bool
+ // description: force delete
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/pods/{nameOrID}/json pods inspectPod
+ //
+ // Inspect Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '200':
+ // $ref: "#/responses/InspectPodResponse"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/json"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/pods/{nameOrID}/exists pods podExists
+ //
+ // Inspect Pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: pod exists
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/pods/{nameOrID}/kill pods killPod
+ //
+ // Kill a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: signal
+ // type: int
+ // description: signal to be sent to pod
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '409':
+ // $ref: "#/responses/ConflictError"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/pause pods pausePod
+ //
+ // Pause a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/restart pods restartPod
+ //
+ // Restart a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/start pods startPod
+ //
+ // Start a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // $ref: "#/responses/PodAlreadyStartedError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/stop pods stopPod
+ //
+ // Stop a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // - in: query
+ // name: t
+ // type: int
+ // description: timeout
+ // responses:
+ // '204':
+ // description: no error
+ // '304':
+ // $ref: "#/responses/PodAlreadyStoppedError"
+ // '400':
+ // $ref: "#/responses/BadParamError"
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/pods/{nameOrID}/unpause pods unpausePod
+ //
+ // Unpause a pod
+ //
+ // ---
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the pod
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // $ref: "#/responses/NoSuchPod"
+ // '500':
+ // $ref: "#responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/{name:..*}/unpause"), APIHandler(s.Context, libpod.PodUnpause)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_swarm.go b/pkg/api/server/register_swarm.go
new file mode 100644
index 000000000..63d8acfde
--- /dev/null
+++ b/pkg/api/server/register_swarm.go
@@ -0,0 +1,27 @@
+package server
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/mux"
+ "github.com/sirupsen/logrus"
+)
+
+func (s *APIServer) RegisterSwarmHandlers(r *mux.Router) error {
+ r.PathPrefix("/v{version:[0-9.]+}/configs/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/nodes/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/secrets/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/services/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/swarm/").HandlerFunc(noSwarm)
+ r.PathPrefix("/v{version:[0-9.]+}/tasks/").HandlerFunc(noSwarm)
+ return nil
+}
+
+// noSwarm returns http.StatusServiceUnavailable rather than something like http.StatusInternalServerError,
+// this allows the client to decide if they still can talk to us
+func noSwarm(w http.ResponseWriter, r *http.Request) {
+ logrus.Errorf("%s is not a podman supported service", r.URL.String())
+ utils.Error(w, "node is not part of a swarm", http.StatusServiceUnavailable, errors.New("Podman does not support service: "+r.URL.String()))
+}
diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go
new file mode 100644
index 000000000..f0eaeffd2
--- /dev/null
+++ b/pkg/api/server/register_system.go
@@ -0,0 +1,11 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
+ r.Handle(VersionedPath("/system/df"), APIHandler(s.Context, generic.GetDiskUsage))
+ return nil
+}
diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go
new file mode 100644
index 000000000..94216b1b6
--- /dev/null
+++ b/pkg/api/server/register_version.go
@@ -0,0 +1,12 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerVersionHandlers(r *mux.Router) error {
+ r.Handle("/version", APIHandler(s.Context, generic.VersionHandler))
+ r.Handle(VersionedPath("/version"), APIHandler(s.Context, generic.VersionHandler))
+ return nil
+}
diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go
new file mode 100644
index 000000000..8fe5a67e4
--- /dev/null
+++ b/pkg/api/server/register_volumes.go
@@ -0,0 +1,85 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/volumes/create volumes createVolume
+ //
+ // Create a volume
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // description: tbd
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/create", APIHandler(s.Context, libpod.CreateVolume)).Methods(http.MethodPost)
+ r.Handle("/libpod/volumes/json", APIHandler(s.Context, libpod.ListVolumes)).Methods(http.MethodGet)
+ // swagger:operation POST /volumes/prune volumes pruneVolumes
+ //
+ // Prune volumes
+ //
+ // ---
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost)
+ // swagger:operation GET /volumes/{nameOrID}/json volumes inspectVolume
+ //
+ // Inspect volume
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the volume
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/InspectVolumeResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchVolume"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/{name:..*}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet)
+ // swagger:operation DELETE /volumes/{nameOrID} volumes removeVolume
+ //
+ // Inspect volume
+ //
+ // ---
+ // parameters:
+ // - in: path
+ // name: nameOrID
+ // required: true
+ // description: the name or ID of the volume
+ // - in: query
+ // name: force
+ // type: bool
+ // description: force removal
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '400':
+ // "$ref": "#/responses/BadParamError"
+ // '404':
+ // "$ref": "#/responses/NoSuchVolume"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle("/libpod/volumes/{name:..*}", APIHandler(s.Context, libpod.RemoveVolume)).Methods(http.MethodDelete)
+ return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
new file mode 100644
index 000000000..717c7a876
--- /dev/null
+++ b/pkg/api/server/server.go
@@ -0,0 +1,189 @@
+// Package serviceapi Provides a Container compatible interface.
+//
+// This documentation describes the HTTP LibPod interface
+//
+// Schemes: http, https
+// Host: podman.io
+// BasePath: /
+// Version: 0.0.1
+// License: Apache-2.0 https://opensource.org/licenses/Apache-2.0
+// Contact: Podman <podman@lists.podman.io> https://podman.io/community/
+//
+// Consumes:
+// - application/json
+// - application/x-tar
+//
+// Produces:
+// - application/json
+// - text/plain
+// - text/html
+//
+// tags:
+// - name: "Containers"
+// description: manage containers
+// - name: "Images"
+// description: manage images
+// - name: "System"
+// description: manage system resources
+//
+// swagger:meta
+package server
+
+import (
+ "context"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "time"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/coreos/go-systemd/activation"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+)
+
+type APIServer struct {
+ http.Server // Where the HTTP work happens
+ *schema.Decoder // Decoder for Query parameters to structs
+ context.Context // Context for graceful server shutdown
+ *libpod.Runtime // Where the real work happens
+ net.Listener // mux for routing HTTP API calls to libpod routines
+ context.CancelFunc // Stop APIServer
+ *time.Timer // Hold timer for sliding window
+ time.Duration // Duration of client access sliding window
+}
+
+// NewServer will create and configure a new API HTTP server
+func NewServer(runtime *libpod.Runtime) (*APIServer, error) {
+ listeners, err := activation.Listeners()
+ if err != nil {
+ return nil, errors.Wrap(err, "Cannot retrieve file descriptors from systemd")
+ }
+ if len(listeners) != 1 {
+ return nil, errors.Errorf("Wrong number of file descriptors from systemd for socket activation (%d != 1)", len(listeners))
+ }
+
+ quit := make(chan os.Signal, 1)
+ signal.Notify(quit)
+
+ router := mux.NewRouter()
+
+ server := APIServer{
+ Server: http.Server{
+ Handler: router,
+ ReadHeaderTimeout: 20 * time.Second,
+ ReadTimeout: 20 * time.Second,
+ WriteTimeout: 2 * time.Minute,
+ },
+ Decoder: schema.NewDecoder(),
+ Context: nil,
+ Runtime: runtime,
+ Listener: listeners[0],
+ CancelFunc: nil,
+ Duration: 300 * time.Second,
+ }
+ server.Timer = time.AfterFunc(server.Duration, func() {
+ if err := server.Shutdown(); err != nil {
+ log.Errorf("unable to shutdown server: %q", err)
+ }
+ })
+
+ ctx, cancelFn := context.WithCancel(context.Background())
+
+ // TODO: Use ConnContext when ported to go 1.13
+ ctx = context.WithValue(ctx, "decoder", server.Decoder)
+ ctx = context.WithValue(ctx, "runtime", runtime)
+ ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown)
+ server.Context = ctx
+
+ server.CancelFunc = cancelFn
+ server.Decoder.IgnoreUnknownKeys(true)
+
+ router.NotFoundHandler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ // We can track user errors...
+ log.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusNotFound, http.StatusText(http.StatusNotFound), r.Method, r.URL.String())
+ http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ },
+ )
+
+ for _, fn := range []func(*mux.Router) error{
+ server.RegisterAuthHandlers,
+ server.RegisterContainersHandlers,
+ server.RegisterDistributionHandlers,
+ server.registerHealthCheckHandlers,
+ server.registerImagesHandlers,
+ server.registerInfoHandlers,
+ server.RegisterMonitorHandlers,
+ server.registerPingHandlers,
+ server.RegisterPluginsHandlers,
+ server.registerPodsHandlers,
+ server.RegisterSwarmHandlers,
+ server.registerSystemHandlers,
+ server.registerVersionHandlers,
+ server.registerVolumeHandlers,
+ } {
+ if err := fn(router); err != nil {
+ return nil, err
+ }
+ }
+
+ if log.IsLevelEnabled(log.DebugLevel) {
+ router.Walk(func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error { // nolint
+ path, err := route.GetPathTemplate()
+ if err != nil {
+ path = ""
+ }
+ methods, err := route.GetMethods()
+ if err != nil {
+ methods = []string{}
+ }
+ log.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path)
+ return nil
+ })
+ }
+
+ return &server, nil
+}
+
+// Serve starts responding to HTTP requests
+func (s *APIServer) Serve() error {
+ defer s.CancelFunc()
+
+ err := s.Server.Serve(s.Listener)
+ if err != nil && err != http.ErrServerClosed {
+ return errors.Wrap(err, "Failed to start APIServer")
+ }
+
+ return nil
+}
+
+// Shutdown is a clean shutdown waiting on existing clients
+func (s *APIServer) Shutdown() error {
+ // We're still in the sliding service window
+ if s.Timer.Stop() {
+ s.Timer.Reset(s.Duration)
+ return nil
+ }
+
+ // We've been idle for the service window, really shutdown
+ go func() {
+ err := s.Server.Shutdown(s.Context)
+ if err != nil && err != context.Canceled {
+ log.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error())
+ }
+ }()
+
+ // Wait for graceful shutdown vs. just killing connections and dropping data
+ <-s.Context.Done()
+ return nil
+}
+
+// Close immediately stops responding to clients and exits
+func (s *APIServer) Close() error {
+ return s.Server.Close()
+}
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
new file mode 100644
index 000000000..0eb57ebab
--- /dev/null
+++ b/pkg/api/server/swagger.go
@@ -0,0 +1,133 @@
+package server
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+// No such image
+// swagger:response NoSuchImage
+type swagErrNoSuchImage struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such container
+// swagger:response NoSuchContainer
+type swagErrNoSuchContainer struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such volume
+// swagger:response NoSuchVolume
+type swagErrNoSuchVolume struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// No such pod
+// swagger:response NoSuchPod
+type swagErrNoSuchPod struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Internal error
+// swagger:response InternalError
+type swagInternalError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Generic error
+// swagger:response GenericError
+type swagGenericError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Conflict error
+// swagger:response ConflictError
+type swagConflictError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Bad parameter
+// swagger:response BadParamError
+type swagBadParamError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Container already started
+// swagger:response ContainerAlreadyStartedError
+type swagContainerAlreadyStartedError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Container already stopped
+// swagger:response ContainerAlreadyStoppedError
+type swagContainerAlreadyStopped struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Pod already started
+// swagger:response PodAlreadyStartedError
+type swagPodAlreadyStartedError struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Pod already stopped
+// swagger:response PodAlreadyStoppedError
+type swagPodAlreadyStopped struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
+// Image summary
+// swagger:response DockerImageSummary
+type swagImageSummary struct {
+ // in:body
+ Body struct {
+ handlers.ImageSummary
+ }
+}
+
+// List Containers
+// swagger:response DocsListContainer
+type swagListContainers struct {
+ // in:body
+ Body struct {
+ // This causes go-swagger to crash
+ //handlers.Container
+ }
+}