summaryrefslogtreecommitdiff
path: root/pkg/api/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r--pkg/api/handlers/compat/container_start.go60
-rw-r--r--pkg/api/handlers/compat/containers.go (renamed from pkg/api/handlers/generic/containers.go)108
-rw-r--r--pkg/api/handlers/compat/containers_attach.go (renamed from pkg/api/handlers/containers_attach.go)19
-rw-r--r--pkg/api/handlers/compat/containers_create.go (renamed from pkg/api/handlers/generic/containers_create.go)42
-rw-r--r--pkg/api/handlers/compat/containers_pause.go28
-rw-r--r--pkg/api/handlers/compat/containers_prune.go64
-rw-r--r--pkg/api/handlers/compat/containers_restart.go45
-rw-r--r--pkg/api/handlers/compat/containers_start.go51
-rw-r--r--pkg/api/handlers/compat/containers_stats.go (renamed from pkg/api/handlers/generic/containers_stats.go)37
-rw-r--r--pkg/api/handlers/compat/containers_top.go (renamed from pkg/api/handlers/containers_top.go)8
-rw-r--r--pkg/api/handlers/compat/containers_unpause.go28
-rw-r--r--pkg/api/handlers/compat/events.go68
-rw-r--r--pkg/api/handlers/compat/exec.go107
-rw-r--r--pkg/api/handlers/compat/images.go (renamed from pkg/api/handlers/generic/images.go)186
-rw-r--r--pkg/api/handlers/compat/images_build.go (renamed from pkg/api/handlers/images_build.go)33
-rw-r--r--pkg/api/handlers/compat/images_history.go40
-rw-r--r--pkg/api/handlers/compat/images_remove.go58
-rw-r--r--pkg/api/handlers/compat/images_save.go14
-rw-r--r--pkg/api/handlers/compat/images_search.go66
-rw-r--r--pkg/api/handlers/compat/images_tag.go37
-rw-r--r--pkg/api/handlers/compat/info.go (renamed from pkg/api/handlers/generic/info.go)10
-rw-r--r--pkg/api/handlers/compat/ping.go31
-rw-r--r--pkg/api/handlers/compat/swagger.go (renamed from pkg/api/handlers/generic/swagger.go)8
-rw-r--r--pkg/api/handlers/compat/system.go (renamed from pkg/api/handlers/generic/system.go)2
-rw-r--r--pkg/api/handlers/compat/types.go55
-rw-r--r--pkg/api/handlers/compat/unsupported.go (renamed from pkg/api/handlers/unsupported.go)2
-rw-r--r--pkg/api/handlers/compat/version.go (renamed from pkg/api/handlers/generic/version.go)11
-rw-r--r--pkg/api/handlers/containers.go247
-rw-r--r--pkg/api/handlers/decoder.go13
-rw-r--r--pkg/api/handlers/events.go41
-rw-r--r--pkg/api/handlers/generic/config.go9
-rw-r--r--pkg/api/handlers/generic/ping.go25
-rw-r--r--pkg/api/handlers/handler.go47
-rw-r--r--pkg/api/handlers/images.go202
-rw-r--r--pkg/api/handlers/libpod/containers.go240
-rw-r--r--pkg/api/handlers/libpod/containers_create.go29
-rw-r--r--pkg/api/handlers/libpod/healthcheck.go30
-rw-r--r--pkg/api/handlers/libpod/images.go391
-rw-r--r--pkg/api/handlers/libpod/manifests.go166
-rw-r--r--pkg/api/handlers/libpod/networks.go85
-rw-r--r--pkg/api/handlers/libpod/pods.go325
-rw-r--r--pkg/api/handlers/libpod/swagger.go94
-rw-r--r--pkg/api/handlers/libpod/types.go82
-rw-r--r--pkg/api/handlers/libpod/volumes.go141
-rw-r--r--pkg/api/handlers/swagger.go44
-rw-r--r--pkg/api/handlers/types.go161
-rw-r--r--pkg/api/handlers/utils/containers.go118
-rw-r--r--pkg/api/handlers/utils/errors.go11
-rw-r--r--pkg/api/handlers/utils/handler.go26
-rw-r--r--pkg/api/handlers/utils/images.go29
-rw-r--r--pkg/api/handlers/utils/pods.go84
51 files changed, 2574 insertions, 1284 deletions
diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/container_start.go
new file mode 100644
index 000000000..d26ef2c82
--- /dev/null
+++ b/pkg/api/handlers/compat/container_start.go
@@ -0,0 +1,60 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func StopContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ // /{version}/containers/(name)/stop
+ query := struct {
+ Timeout int `schema:"t"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "unable to get state for Container %s", name))
+ return
+ }
+ // If the Container is stopped already, send a 304
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ utils.WriteResponse(w, http.StatusNotModified, "")
+ return
+ }
+
+ var stopError error
+ if query.Timeout > 0 {
+ stopError = con.StopWithTimeout(uint(query.Timeout))
+ } else {
+ stopError = con.Stop()
+ }
+ if stopError != nil {
+ utils.InternalServerError(w, errors.Wrapf(stopError, "failed to stop %s", name))
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/compat/containers.go
index 8dc73ae14..2ce113d30 100644
--- a/pkg/api/handlers/generic/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"encoding/binary"
@@ -10,11 +10,12 @@ import (
"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/signal"
"github.com/containers/libpod/pkg/util"
- "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@@ -35,12 +36,26 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- if query.Link {
+
+ if query.Link && !utils.IsLibpodRequest(r) {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
utils.ErrLinkNotSupport)
return
}
- utils.RemoveContainer(w, r, query.Force, query.Vols)
+
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
@@ -58,6 +73,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
}{
// override any golang type defaults
}
+
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
@@ -71,7 +87,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- if _, found := mux.Vars(r)["limit"]; found {
+ if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
@@ -86,7 +102,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
var list = make([]*handlers.Container, len(containers))
for i, ctnr := range containers {
- api, err := handlers.LibpodToContainer(ctnr, infoData)
+ api, err := handlers.LibpodToContainer(ctnr, infoData, query.Size)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -98,14 +114,25 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
func GetContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Size bool `schema:"size"`
+ }{
+ // override any golang type defaults
+ }
- name := mux.Vars(r)["name"]
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
- api, err := handlers.LibpodToContainerJSON(ctnr)
+ api, err := handlers.LibpodToContainerJSON(ctnr, query.Size)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -115,18 +142,57 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
func KillContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/kill
- con, err := utils.KillContainer(w, r)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Signal string `schema:"signal"`
+ }{
+ Signal: "KILL",
+ }
+ 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
+ }
+
+ sig, err := signal.ParseSignalNameOrNumber(query.Signal)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
if err != nil {
+ utils.ContainerNotFound(w, name, err)
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()))
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
return
}
+
+ // If the Container is stopped already, send a 409
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ utils.Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name)))
+ return
+ }
+
+ err = con.Kill(uint(sig))
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
+ }
+
+ if utils.IsLibpodRequest(r) {
+ // the kill behavior for docker differs from podman in that they appear to wait
+ // for the Container to croak so the exit code is accurate immediately after the
+ // kill is sent. libpod does not. but we can add a wait here only for the docker
+ // side of things and mimic that behavior
+ if _, err = con.Wait(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
+ return
+ }
+ }
// Success
utils.WriteResponse(w, http.StatusNoContent, "")
}
@@ -136,7 +202,7 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
- msg = err.Error()
+ return
}
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
@@ -174,7 +240,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -191,7 +257,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
var since time.Time
- if _, found := mux.Vars(r)["since"]; found {
+ if _, found := r.URL.Query()["since"]; found {
since, err = util.ParseInputTime(query.Since)
if err != nil {
utils.BadRequest(w, "since", query.Since, err)
@@ -200,7 +266,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
var until time.Time
- if _, found := mux.Vars(r)["until"]; found {
+ if _, found := r.URL.Query()["until"]; found {
since, err = util.ParseInputTime(query.Until)
if err != nil {
utils.BadRequest(w, "until", query.Until, err)
@@ -233,7 +299,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var builder strings.Builder
for ok := true; ok; ok = query.Follow {
for line := range logChannel {
- if _, found := mux.Vars(r)["until"]; found {
+ if _, found := r.URL.Query()["until"]; found {
if line.Time.After(until) {
break
}
@@ -266,7 +332,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
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 {
@@ -275,7 +340,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
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/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go
index eb306348b..da7b5bb0c 100644
--- a/pkg/api/handlers/containers_attach.go
+++ b/pkg/api/handlers/compat/containers_attach.go
@@ -1,4 +1,4 @@
-package handlers
+package compat
import (
"net/http"
@@ -6,7 +6,6 @@ import (
"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"
"github.com/sirupsen/logrus"
@@ -30,12 +29,10 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
- muxVars := mux.Vars(r)
-
// Detach keys: explicitly set to "" is very different from unset
// TODO: Our format for parsing these may be different from Docker.
var detachKeys *string
- if _, found := muxVars["detachKeys"]; found {
+ if _, found := r.URL.Query()["detachKeys"]; found {
detachKeys = &query.DetachKeys
}
@@ -44,15 +41,15 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
streams.Stderr = true
streams.Stdin = true
useStreams := false
- if _, found := muxVars["stdin"]; found {
+ if _, found := r.URL.Query()["stdin"]; found {
streams.Stdin = query.Stdin
useStreams = true
}
- if _, found := muxVars["stdout"]; found {
+ if _, found := r.URL.Query()["stdout"]; found {
streams.Stdout = query.Stdout
useStreams = true
}
- if _, found := muxVars["stderr"]; found {
+ if _, found := r.URL.Query()["stderr"]; found {
streams.Stderr = query.Stderr
useStreams = true
}
@@ -72,12 +69,12 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
// We only support stream=true or unset
- if _, found := muxVars["stream"]; found && query.Stream {
+ if _, found := r.URL.Query()["stream"]; found && query.Stream {
utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported"))
return
}
- name := getName(r)
+ name := utils.GetName(r)
ctr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -138,7 +135,7 @@ func ResizeContainer(w http.ResponseWriter, r *http.Request) {
return
}
- name := getName(r)
+ name := utils.GetName(r)
ctr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index edefd5757..12af40876 100644
--- a/pkg/api/handlers/generic/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"encoding/json"
@@ -6,19 +6,17 @@ import (
"net/http"
"strings"
- "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/libpod/define"
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/signal"
createconfig "github.com/containers/libpod/pkg/spec"
"github.com/containers/storage"
- "github.com/docker/docker/pkg/signal"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -48,39 +46,21 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()"))
return
}
- cc, err := makeCreateConfig(input, newImage)
+ defaultContainerConfig, err := runtime.GetConfig()
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()"))
return
}
-
- cc.Name = query.Name
- var pod *libpod.Pod
- ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod)
+ cc, err := makeCreateConfig(defaultContainerConfig, input, newImage)
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()"))
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
}
-
- response := ContainerCreateResponse{
- Id: ctr.ID(),
- Warnings: []string{}}
-
- utils.WriteResponse(w, http.StatusCreated, response)
+ cc.Name = query.Name
+ utils.CreateContainer(r.Context(), w, runtime, &cc)
}
-func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool
@@ -101,7 +81,7 @@ func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Ima
workDir = input.WorkingDir
}
- stopTimeout := uint(define.CtrRemoveTimeout)
+ stopTimeout := defaultContainerConfig.Engine.StopTimeout
if input.StopTimeout != nil {
stopTimeout = uint(*input.StopTimeout)
}
diff --git a/pkg/api/handlers/compat/containers_pause.go b/pkg/api/handlers/compat/containers_pause.go
new file mode 100644
index 000000000..060bdbaeb
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_pause.go
@@ -0,0 +1,28 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+func PauseContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /{version}/containers/(name)/pause
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ // the api does not error if the Container is already paused, so just into it
+ if err := con.Pause(); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/compat/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go
new file mode 100644
index 000000000..a56c3903d
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_prune.go
@@ -0,0 +1,64 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/docker/docker/api/types"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PruneContainers(w http.ResponseWriter, r *http.Request) {
+ var (
+ delContainers []string
+ space int64
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ Filters map[string][]string `schema:"filters"`
+ }{}
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ filterFuncs, err := utils.GenerateFilterFuncsFromMap(runtime, query.Filters)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Libpod response differs
+ if utils.IsLibpodRequest(r) {
+ var response []handlers.LibpodContainersPruneReport
+ for ctrID, size := range prunedContainers {
+ response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size})
+ }
+ for ctrID, err := range pruneErrors {
+ response = append(response, handlers.LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()})
+ }
+ utils.WriteResponse(w, http.StatusOK, response)
+ return
+ }
+ for ctrID, size := range prunedContainers {
+ if pruneErrors[ctrID] == nil {
+ space += size
+ delContainers = append(delContainers, ctrID)
+ }
+ }
+ report := types.ContainersPruneReport{
+ ContainersDeleted: delContainers,
+ SpaceReclaimed: uint64(space),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go
new file mode 100644
index 000000000..343bf96d2
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_restart.go
@@ -0,0 +1,45 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func RestartContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ // /{version}/containers/(name)/restart
+ query := struct {
+ Timeout int `schema:"t"`
+ }{
+ // Override golang default values for types
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.BadRequest(w, "url", r.URL.String(), errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ timeout := con.StopTimeout()
+ if _, found := r.URL.Query()["t"]; found {
+ timeout = uint(query.Timeout)
+ }
+
+ if err := con.RestartWithTimeout(r.Context(), timeout); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
new file mode 100644
index 000000000..67bd287ab
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -0,0 +1,51 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func StartContainer(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ DetachKeys string `schema:"detachKeys"`
+ }{
+ // Override golang default values for types
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.BadRequest(w, "url", r.URL.String(), err)
+ return
+ }
+ if len(query.DetachKeys) > 0 {
+ // TODO - start does not support adding detach keys
+ utils.BadRequest(w, "detachKeys", query.DetachKeys, errors.New("the detachKeys parameter is not supported yet"))
+ return
+ }
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ state, err := con.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state == define.ContainerStateRunning {
+ utils.WriteResponse(w, http.StatusNotModified, "")
+ return
+ }
+ if err := con.Start(r.Context(), false); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index e33d37606..53ad0a632 100644
--- a/pkg/api/handlers/generic/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"encoding/json"
@@ -7,11 +7,9 @@ import (
"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"
@@ -20,9 +18,6 @@ import (
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)
@@ -36,7 +31,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -62,17 +57,19 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
var preRead time.Time
- var preCPUStats docker.CPUStats
+ var preCPUStats CPUStats
if query.Stream {
preRead = time.Now()
- preCPUStats = docker.CPUStats{
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
+ preCPUStats = CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
- PercpuUsage: []uint64{uint64(stats.CPU)},
- UsageInKernelmode: 0,
- UsageInUsermode: 0,
+ PercpuUsage: stats.PerCPU,
+ UsageInKernelmode: stats.CPUSystemNano,
+ UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
},
- SystemUsage: 0,
+ CPU: stats.CPU,
+ SystemUsage: systemUsage,
OnlineCPUs: 0,
ThrottlingData: docker.ThrottlingData{},
}
@@ -126,8 +123,9 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
InstanceID: "",
}
- s := handlers.Stats{StatsJSON: docker.StatsJSON{
- Stats: docker.Stats{
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
+ s := StatsJSON{
+ Stats: Stats{
Read: time.Now(),
PreRead: preRead,
PidsStats: docker.PidsStats{
@@ -144,14 +142,15 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
IoTimeRecursive: nil,
SectorsRecursive: nil,
},
- CPUStats: docker.CPUStats{
+ CPUStats: CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: cgroupStat.CPU.Usage.Total,
- PercpuUsage: []uint64{uint64(stats.CPU)},
+ PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
},
- SystemUsage: 0,
+ CPU: stats.CPU,
+ SystemUsage: systemUsage,
OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
ThrottlingData: docker.ThrottlingData{
Periods: 0,
@@ -174,7 +173,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
Name: stats.Name,
ID: stats.ContainerID,
Networks: net,
- }}
+ }
utils.WriteJSON(w, http.StatusOK, s)
if flusher, ok := w.(http.Flusher); ok {
diff --git a/pkg/api/handlers/containers_top.go b/pkg/api/handlers/compat/containers_top.go
index 6b7688eb0..202be55d1 100644
--- a/pkg/api/handlers/containers_top.go
+++ b/pkg/api/handlers/compat/containers_top.go
@@ -1,12 +1,12 @@
-package handlers
+package compat
import (
"net/http"
"strings"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
- "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -30,7 +30,7 @@ func TopContainer(w http.ResponseWriter, r *http.Request) {
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
c, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -43,7 +43,7 @@ func TopContainer(w http.ResponseWriter, r *http.Request) {
return
}
- var body = ContainerTopOKBody{}
+ var body = handlers.ContainerTopOKBody{}
if len(output) > 0 {
body.Titles = strings.Split(output[0], "\t")
for _, line := range output[1:] {
diff --git a/pkg/api/handlers/compat/containers_unpause.go b/pkg/api/handlers/compat/containers_unpause.go
new file mode 100644
index 000000000..adabdeaea
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_unpause.go
@@ -0,0 +1,28 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+func UnpauseContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /{version}/containers/(name)/unpause
+ name := utils.GetName(r)
+ con, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+
+ if err := con.Unpause(); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ // Success
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
new file mode 100644
index 000000000..0f72ef328
--- /dev/null
+++ b/pkg/api/handlers/compat/events.go
@@ -0,0 +1,68 @@
+package compat
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/events"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func GetEvents(w http.ResponseWriter, r *http.Request) {
+ var (
+ fromStart bool
+ eventsError error
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+
+ query := struct {
+ Since string `schema:"since"`
+ Until string `schema:"until"`
+ Filters map[string][]string `schema:"filters"`
+ }{}
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ }
+
+ var libpodFilters = []string{}
+ if _, found := r.URL.Query()["filters"]; found {
+ for k, v := range query.Filters {
+ libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
+ }
+ }
+
+ if len(query.Since) > 0 || len(query.Until) > 0 {
+ fromStart = true
+ }
+ eventChannel := make(chan *events.Event)
+ go func() {
+ readOpts := events.ReadOptions{FromStart: fromStart, Stream: true, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until}
+ eventsError = runtime.Events(readOpts)
+ }()
+ if eventsError != nil {
+ utils.InternalServerError(w, eventsError)
+ return
+ }
+
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ for event := range eventChannel {
+ e := handlers.EventToApiEvent(event)
+ if err := coder.Encode(e); err != nil {
+ logrus.Errorf("unable to write json: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+}
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go
new file mode 100644
index 000000000..ec1a8ac96
--- /dev/null
+++ b/pkg/api/handlers/compat/exec.go
@@ -0,0 +1,107 @@
+package compat
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "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/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ExecCreateHandler creates an exec session for a given container.
+func ExecCreateHandler(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ input := new(handlers.ExecCreateConfig)
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON"))
+ return
+ }
+
+ ctrName := utils.GetName(r)
+ ctr, err := runtime.LookupContainer(ctrName)
+ if err != nil {
+ utils.ContainerNotFound(w, ctrName, err)
+ return
+ }
+
+ libpodConfig := new(libpod.ExecConfig)
+ libpodConfig.Command = input.Cmd
+ libpodConfig.Terminal = input.Tty
+ libpodConfig.AttachStdin = input.AttachStdin
+ libpodConfig.AttachStderr = input.AttachStderr
+ libpodConfig.AttachStdout = input.AttachStdout
+ if input.DetachKeys != "" {
+ libpodConfig.DetachKeys = &input.DetachKeys
+ }
+ libpodConfig.Environment = make(map[string]string)
+ for _, envStr := range input.Env {
+ split := strings.SplitN(envStr, "=", 2)
+ if len(split) != 2 {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr))
+ return
+ }
+ libpodConfig.Environment[split[0]] = split[1]
+ }
+ libpodConfig.WorkDir = input.WorkingDir
+ libpodConfig.Privileged = input.Privileged
+ libpodConfig.User = input.User
+
+ sessID, err := ctr.ExecCreate(libpodConfig)
+ if err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid {
+ // Check if the container is paused. If so, return a 409
+ state, err := ctr.State()
+ if err == nil {
+ // Ignore the error != nil case. We're already
+ // throwing an InternalServerError below.
+ if state == define.ContainerStatePaused {
+ utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID()))
+ return
+ }
+ }
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ resp := new(handlers.ExecCreateResponse)
+ resp.ID = sessID
+
+ utils.WriteResponse(w, http.StatusCreated, resp)
+}
+
+// ExecInspectHandler inspects a given exec session.
+func ExecInspectHandler(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ sessionID := mux.Vars(r)["id"]
+ sessionCtr, err := runtime.GetExecSessionContainer(sessionID)
+ if err != nil {
+ utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err)
+ return
+ }
+
+ logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID())
+
+ session, err := sessionCtr.ExecSession(sessionID)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID()))
+ return
+ }
+
+ inspectOut, err := session.Inspect()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, inspectOut)
+}
diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/compat/images.go
index 93adb7f69..ea9cbd691 100644
--- a/pkg/api/handlers/generic/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -1,12 +1,12 @@
-package generic
+package compat
import (
"encoding/json"
"fmt"
+ "io"
"io/ioutil"
"net/http"
"os"
- "strconv"
"strings"
"github.com/containers/buildah"
@@ -15,13 +15,11 @@ import (
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/domain/entities"
"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) {
@@ -29,7 +27,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
// 500 server
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
@@ -59,16 +57,14 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
}
func PruneImages(w http.ResponseWriter, r *http.Request) {
- // 200 no error
- // 500 internal
var (
- dangling = true
- err error
+ filters []string
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
+ All bool
Filters map[string][]string `schema:"filters"`
}{
// This is where you can override the golang default value for one of fields
@@ -79,60 +75,24 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
return
}
- // until ts is not supported on podman prune
- if v, found := query.Filters["until"]; found {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "until=%s is not supported yet", v))
- return
- }
- // labels are not supported on podman prune
- if _, found := query.Filters["since"]; found {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet"))
- return
- }
-
- if v, found := query.Filters["dangling"]; found {
- dangling, err = strconv.ParseBool(v[0])
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
- return
+ idr := []types.ImageDeleteResponseItem{}
+ for k, v := range query.Filters {
+ for _, val := range v {
+ filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
-
- 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{})
+ pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get images to prune"))
+ utils.InternalServerError(w, err)
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)
+ for _, p := range pruneCids {
+ idr = append(idr, types.ImageDeleteResponseItem{
+ Deleted: p,
+ })
}
+
+ //FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden
ipr := types.ImagesPruneReport{
ImagesDeleted: idr,
SpaceReclaimed: 1, // TODO we cannot supply this right now
@@ -148,14 +108,14 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
- author string
- changes string
- comment string
- container string
+ Author string `schema:"author"`
+ Changes string `schema:"changes"`
+ Comment string `schema:"comment"`
+ Container string `schema:"container"`
//fromSrc string # fromSrc is currently unused
- pause bool
- repo string
- tag string
+ Pause bool `schema:"pause"`
+ Repo string `schema:"repo"`
+ Tag string `schema:"tag"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -169,13 +129,13 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
- sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
tag := "latest"
options := libpod.ContainerCommitOptions{
Pause: true,
}
options.CommitOptions = buildah.CommitOptions{
- SignaturePolicyPath: rtc.SignaturePolicyPath,
+ SignaturePolicyPath: rtc.Engine.SignaturePolicyPath,
ReportWriter: os.Stderr,
SystemContext: sc,
PreferredManifestType: manifest.DockerV2Schema2MediaType,
@@ -187,22 +147,22 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) {
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 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)
+ if len(query.Repo) > 1 {
+ destImage = fmt.Sprintf("%s:%s", query.Repo, tag)
}
commitImage, err := ctr.Commit(r.Context(), destImage, options)
@@ -221,8 +181,8 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
- fromSrc string
- changes []string
+ FromSrc string `schema:"fromSrc"`
+ Changes []string `schema:"changes"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -232,7 +192,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
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
+ source := query.FromSrc
if source == "-" {
f, err := ioutil.TempFile("", "api_load.tar")
if err != nil {
@@ -240,11 +200,11 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
return
}
source = f.Name()
- if err := handlers.SaveFromBody(f, r); err != nil {
+ if err := SaveFromBody(f, r); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
}
}
- iid, err := runtime.Import(r.Context(), source, "", query.changes, "", false)
+ 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
@@ -280,8 +240,8 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
- fromImage string
- tag string
+ FromImage string `schema:"fromImage"`
+ Tag string `schema:"tag"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -296,9 +256,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
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)
+ fromImage := query.FromImage
+ if len(query.Tag) >= 1 {
+ fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
}
// TODO
@@ -327,8 +287,8 @@ 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)
+ name := utils.GetName(r)
+ newImage, err := utils.GetImage(r, name)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
return
@@ -342,14 +302,12 @@ func GetImage(w http.ResponseWriter, r *http.Request) {
}
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)+1)
+ var summaries = make([]*entities.ImageSummary, len(images))
for j, img := range images {
is, err := handlers.ImageToImageSummary(img)
if err != nil {
@@ -360,3 +318,47 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, summaries)
}
+
+func LoadImages(w http.ResponseWriter, r *http.Request) {
+ // TODO this is basically wrong
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Changes map[string]string `json:"changes"`
+ Message string `json:"message"`
+ Quiet bool `json:"quiet"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ var (
+ err error
+ writer io.Writer
+ )
+ f, err := ioutil.TempFile("", "api_load.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
+ return
+ }
+ if err := SaveFromBody(f, r); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
+ return
+ }
+ id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "")
+ //id, err := runtime.Import(r.Context())
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image"))
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, struct {
+ Stream string `json:"stream"`
+ }{
+ Stream: fmt.Sprintf("Loaded image: %s\n", id),
+ })
+}
diff --git a/pkg/api/handlers/images_build.go b/pkg/api/handlers/compat/images_build.go
index b29c45574..e208e6ddc 100644
--- a/pkg/api/handlers/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -1,4 +1,4 @@
-package handlers
+package compat
import (
"bytes"
@@ -15,13 +15,15 @@ import (
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/storage/pkg/archive"
- "github.com/gorilla/mux"
+ "github.com/gorilla/schema"
)
func BuildImage(w http.ResponseWriter, r *http.Request) {
- authConfigs := map[string]AuthConfig{}
+ authConfigs := map[string]handlers.AuthConfig{}
if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 {
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0]))
if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil {
@@ -97,8 +99,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
Outputs: "",
Registry: "docker.io",
}
-
- if err := decodeQuery(r, &query); err != nil {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
@@ -114,24 +116,24 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
tag = tokens[1]
}
- if t, found := mux.Vars(r)["target"]; found {
- name = t
+ if _, found := r.URL.Query()["target"]; found {
+ name = query.Target
}
var buildArgs = map[string]string{}
- if a, found := mux.Vars(r)["buildargs"]; found {
- if err := json.Unmarshal([]byte(a), &buildArgs); err != nil {
- utils.BadRequest(w, "buildargs", a, err)
+ if _, found := r.URL.Query()["buildargs"]; found {
+ if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
+ utils.BadRequest(w, "buildargs", query.BuildArgs, err)
return
}
}
// convert label formats
var labels = []string{}
- if l, found := mux.Vars(r)["labels"]; found {
+ if _, found := r.URL.Query()["labels"]; found {
var m = map[string]string{}
- if err := json.Unmarshal([]byte(l), &m); err != nil {
- utils.BadRequest(w, "labels", l, err)
+ if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
+ utils.BadRequest(w, "labels", query.Labels, err)
return
}
@@ -141,7 +143,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
pullPolicy := buildah.PullIfMissing
- if _, found := mux.Vars(r)["pull"]; found {
+ if _, found := r.URL.Query()["pull"]; found {
if query.Pull {
pullPolicy = buildah.PullAlways
}
@@ -220,7 +222,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
Devices: nil,
}
- id, _, err := getRuntime(r).Build(r.Context(), buildOptions, query.Dockerfile)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile)
if err != nil {
utils.InternalServerError(w, err)
}
diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go
new file mode 100644
index 000000000..afadf4c48
--- /dev/null
+++ b/pkg/api/handlers/compat/images_history.go
@@ -0,0 +1,40 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func HistoryImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+ var allHistory []handlers.HistoryResponse
+
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+
+ }
+ history, err := newImage.History(r.Context())
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for _, h := range history {
+ l := handlers.HistoryResponse{
+ ID: h.ID,
+ Created: h.Created.Unix(),
+ CreatedBy: h.CreatedBy,
+ Tags: h.Tags,
+ Size: h.Size,
+ Comment: h.Comment,
+ }
+ allHistory = append(allHistory, l)
+ }
+ utils.WriteResponse(w, http.StatusOK, allHistory)
+}
diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go
new file mode 100644
index 000000000..ed0153529
--- /dev/null
+++ b/pkg/api/handlers/compat/images_remove.go
@@ -0,0 +1,58 @@
+package compat
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func RemoveImage(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Force bool `schema:"force"`
+ NoPrune bool `schema:"noprune"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if _, found := r.URL.Query()["noprune"]; found {
+ if query.NoPrune {
+ utils.UnSupportedParameter("noprune")
+ }
+ }
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+
+ results, err := runtime.RemoveImage(r.Context(), newImage, query.Force)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ response := make([]map[string]string, 0, len(results.Untagged)+1)
+ deleted := make(map[string]string, 1)
+ deleted["Deleted"] = results.Deleted
+ response = append(response, deleted)
+
+ for _, u := range results.Untagged {
+ untagged := make(map[string]string, 1)
+ untagged["Untagged"] = u
+ response = append(response, untagged)
+ }
+
+ utils.WriteResponse(w, http.StatusOK, response)
+
+}
diff --git a/pkg/api/handlers/compat/images_save.go b/pkg/api/handlers/compat/images_save.go
new file mode 100644
index 000000000..b39c719a0
--- /dev/null
+++ b/pkg/api/handlers/compat/images_save.go
@@ -0,0 +1,14 @@
+package compat
+
+import (
+ "io"
+ "net/http"
+ "os"
+)
+
+func SaveFromBody(f *os.File, r *http.Request) error { // nolint
+ if _, err := io.Copy(f, r.Body); err != nil {
+ return err
+ }
+ return f.Close()
+}
diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go
new file mode 100644
index 000000000..7283b22c4
--- /dev/null
+++ b/pkg/api/handlers/compat/images_search.go
@@ -0,0 +1,66 @@
+package compat
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func SearchImages(w http.ResponseWriter, r *http.Request) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Term string `json:"term"`
+ Limit int `json:"limit"`
+ Filters map[string][]string `json:"filters"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ filter := image.SearchFilter{}
+ if len(query.Filters) > 0 {
+ if len(query.Filters["stars"]) > 0 {
+ stars, err := strconv.Atoi(query.Filters["stars"][0])
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filter.Stars = stars
+ }
+ if len(query.Filters["is-official"]) > 0 {
+ isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0])
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filter.IsOfficial = types.NewOptionalBool(isOfficial)
+ }
+ if len(query.Filters["is-automated"]) > 0 {
+ isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0])
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filter.IsAutomated = types.NewOptionalBool(isAutomated)
+ }
+ }
+ options := image.SearchOptions{
+ Filter: filter,
+ Limit: query.Limit,
+ }
+ results, err := image.SearchImages(query.Term, options)
+ if err != nil {
+ utils.BadRequest(w, "term", query.Term, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, results)
+}
diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go
new file mode 100644
index 000000000..722be5653
--- /dev/null
+++ b/pkg/api/handlers/compat/images_tag.go
@@ -0,0 +1,37 @@
+package compat
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func TagImage(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // /v1.xx/images/(name)/tag
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+ tag := "latest"
+ if len(r.Form.Get("tag")) > 0 {
+ tag = r.Form.Get("tag")
+ }
+ if len(r.Form.Get("repo")) < 1 {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("repo parameter is required to tag an image"))
+ return
+ }
+ repo := r.Form.Get("repo")
+ tagName := fmt.Sprintf("%s:%s", repo, tag)
+ if err := newImage.TagImage(tagName); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusCreated, "")
+}
diff --git a/pkg/api/handlers/generic/info.go b/pkg/api/handlers/compat/info.go
index c9e79233d..104d0793b 100644
--- a/pkg/api/handlers/generic/info.go
+++ b/pkg/api/handlers/compat/info.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"fmt"
@@ -9,8 +9,8 @@ import (
"strings"
"time"
+ "github.com/containers/common/pkg/config"
"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"
@@ -60,7 +60,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
CPUCfsQuota: sysInfo.CPUCfsQuota,
CPUSet: sysInfo.Cpuset,
CPUShares: sysInfo.CPUShares,
- CgroupDriver: configInfo.CgroupManager,
+ CgroupDriver: configInfo.Engine.CgroupManager,
ClusterAdvertise: "",
ClusterStore: "",
ContainerdCommit: docker.Commit{},
@@ -69,7 +69,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
ContainersRunning: stateInfo[define.ContainerStateRunning],
ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited],
Debug: log.IsLevelEnabled(log.DebugLevel),
- DefaultRuntime: configInfo.OCIRuntime,
+ DefaultRuntime: configInfo.Engine.OCIRuntime,
DockerRootDir: storeInfo["GraphRoot"].(string),
Driver: storeInfo["GraphDriverName"].(string),
DriverStatus: getGraphStatus(storeInfo),
@@ -152,7 +152,7 @@ func getSecOpts(sysInfo *sysinfo.SysInfo) []string {
func getRuntimes(configInfo *config.Config) map[string]docker.Runtime {
var runtimes = map[string]docker.Runtime{}
- for name, paths := range configInfo.OCIRuntimes {
+ for name, paths := range configInfo.Engine.OCIRuntimes {
runtimes[name] = docker.Runtime{
Path: paths[0],
Args: nil,
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
new file mode 100644
index 000000000..6e77e270f
--- /dev/null
+++ b/pkg/api/handlers/compat/ping.go
@@ -0,0 +1,31 @@
+package compat
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/buildah"
+ "github.com/containers/libpod/pkg/api/handlers"
+)
+
+// Ping returns headers to client about the service
+//
+// This handler must always be the same for the compatibility and libpod URL trees!
+// Clients will use the Header availability to test which backend engine is in use.
+func Ping(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("BuildKit-Version", "")
+ w.Header().Set("Docker-Experimental", "true")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Pragma", "no-cache")
+
+ // API-Version and Libpod-API-Version may not always be equal
+ w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("Libpod-Buildha-Version", buildah.Version)
+ w.WriteHeader(http.StatusOK)
+
+ if r.Method == http.MethodGet {
+ fmt.Fprint(w, "OK")
+ }
+ fmt.Fprint(w, "\n")
+}
diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/compat/swagger.go
index 27e1fc18d..cbd8e61fb 100644
--- a/pkg/api/handlers/generic/swagger.go
+++ b/pkg/api/handlers/compat/swagger.go
@@ -1,11 +1,15 @@
-package generic
+package compat
+
+import (
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
// Create container
// swagger:response ContainerCreateResponse
type swagCtrCreateResponse struct {
// in:body
Body struct {
- ContainerCreateResponse
+ utils.ContainerCreateResponse
}
}
diff --git a/pkg/api/handlers/generic/system.go b/pkg/api/handlers/compat/system.go
index edf1f8522..47e187ba1 100644
--- a/pkg/api/handlers/generic/system.go
+++ b/pkg/api/handlers/compat/system.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"net/http"
diff --git a/pkg/api/handlers/compat/types.go b/pkg/api/handlers/compat/types.go
new file mode 100644
index 000000000..b8d06760f
--- /dev/null
+++ b/pkg/api/handlers/compat/types.go
@@ -0,0 +1,55 @@
+package compat
+
+import (
+ "time"
+
+ docker "github.com/docker/docker/api/types"
+)
+
+// CPUStats aggregates and wraps all CPU related info of container
+type CPUStats struct {
+ // CPU Usage. Linux and Windows.
+ CPUUsage docker.CPUUsage `json:"cpu_usage"`
+
+ // System Usage. Linux only.
+ SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
+
+ // Online CPUs. Linux only.
+ OnlineCPUs uint32 `json:"online_cpus,omitempty"`
+
+ // Usage of CPU in %. Linux only.
+ CPU float64 `json:"cpu"`
+
+ // Throttling Data. Linux only.
+ ThrottlingData docker.ThrottlingData `json:"throttling_data,omitempty"`
+}
+
+// Stats is Ultimate struct aggregating all types of stats of one container
+type Stats struct {
+ // Common stats
+ Read time.Time `json:"read"`
+ PreRead time.Time `json:"preread"`
+
+ // Linux specific stats, not populated on Windows.
+ PidsStats docker.PidsStats `json:"pids_stats,omitempty"`
+ BlkioStats docker.BlkioStats `json:"blkio_stats,omitempty"`
+
+ // Windows specific stats, not populated on Linux.
+ NumProcs uint32 `json:"num_procs"`
+ StorageStats docker.StorageStats `json:"storage_stats,omitempty"`
+
+ // Shared stats
+ CPUStats CPUStats `json:"cpu_stats,omitempty"`
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
+ MemoryStats docker.MemoryStats `json:"memory_stats,omitempty"`
+}
+
+type StatsJSON struct {
+ Stats
+
+ Name string `json:"name,omitempty"`
+ ID string `json:"id,omitempty"`
+
+ // Networks request version >=1.21
+ Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
+}
diff --git a/pkg/api/handlers/unsupported.go b/pkg/api/handlers/compat/unsupported.go
index 956d31f8b..d9c3c3f49 100644
--- a/pkg/api/handlers/unsupported.go
+++ b/pkg/api/handlers/compat/unsupported.go
@@ -1,4 +1,4 @@
-package handlers
+package compat
import (
"fmt"
diff --git a/pkg/api/handlers/generic/version.go b/pkg/api/handlers/compat/version.go
index 39423914d..c7f7917ac 100644
--- a/pkg/api/handlers/generic/version.go
+++ b/pkg/api/handlers/compat/version.go
@@ -1,4 +1,4 @@
-package generic
+package compat
import (
"fmt"
@@ -14,11 +14,6 @@ import (
"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
@@ -41,14 +36,14 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
Name: "Podman Engine",
Version: versionInfo.Version,
Details: map[string]string{
- "APIVersion": DefaultApiVersion,
+ "APIVersion": handlers.DefaultApiVersion,
"Arch": goRuntime.GOARCH,
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
"Experimental": "true",
"GitCommit": versionInfo.GitCommit,
"GoVersion": versionInfo.GoVersion,
"KernelVersion": hostInfo["kernel"].(string),
- "MinAPIVersion": MinimalApiVersion,
+ "MinAPIVersion": handlers.MinimalApiVersion,
"Os": goRuntime.GOOS,
},
}}
diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go
deleted file mode 100644
index b5c78ce53..000000000
--- a/pkg/api/handlers/containers.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package handlers
-
-import (
- "fmt"
- "github.com/docker/docker/api/types"
- "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, "")
-}
-
-func PruneContainers(w http.ResponseWriter, r *http.Request) {
- var (
- delContainers []string
- space int64
- )
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
- decoder := r.Context().Value("decoder").(*schema.Decoder)
-
- query := struct {
- Filters map[string][]string `schema:"filter"`
- }{}
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- return
- }
-
- filterFuncs, err := utils.GenerateFilterFuncsFromMap(runtime, query.Filters)
- if err != nil {
- utils.InternalServerError(w, err)
- return
- }
- prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
- if err != nil {
- utils.InternalServerError(w, err)
- return
- }
-
- // Libpod response differs
- if utils.IsLibpodRequest(r) {
- var response []LibpodContainersPruneReport
- for ctrID, size := range prunedContainers {
- response = append(response, LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size})
- }
- for ctrID, err := range pruneErrors {
- response = append(response, LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()})
- }
- utils.WriteResponse(w, http.StatusOK, response)
- return
- }
- for ctrID, size := range prunedContainers {
- if pruneErrors[ctrID] == nil {
- space += size
- delContainers = append(delContainers, ctrID)
- }
- }
- report := types.ContainersPruneReport{
- ContainersDeleted: delContainers,
- SpaceReclaimed: uint64(space),
- }
- utils.WriteResponse(w, http.StatusOK, report)
-}
diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go
index 890d77ecc..03b86275d 100644
--- a/pkg/api/handlers/decoder.go
+++ b/pkg/api/handlers/decoder.go
@@ -3,8 +3,10 @@ package handlers
import (
"encoding/json"
"reflect"
+ "syscall"
"time"
+ "github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
@@ -17,6 +19,9 @@ func NewAPIDecoder() *schema.Decoder {
d.IgnoreUnknownKeys(true)
d.RegisterConverter(map[string][]string{}, convertUrlValuesString)
d.RegisterConverter(time.Time{}, convertTimeString)
+
+ var Signal syscall.Signal
+ d.RegisterConverter(Signal, convertSignal)
return d
}
@@ -89,3 +94,11 @@ func convertTimeString(query string) reflect.Value {
func ParseDateTime(query string) time.Time {
return convertTimeString(query).Interface().(time.Time)
}
+
+func convertSignal(query string) reflect.Value {
+ signal, err := util.ParseSignal(query)
+ if err != nil {
+ logrus.Infof("convertSignal: Failed to parse %s: %s", query, err.Error())
+ }
+ return reflect.ValueOf(signal)
+}
diff --git a/pkg/api/handlers/events.go b/pkg/api/handlers/events.go
deleted file mode 100644
index 44bf35254..000000000
--- a/pkg/api/handlers/events.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package handlers
-
-import (
- "fmt"
- "net/http"
- "strings"
- "time"
-
- "github.com/containers/libpod/pkg/api/handlers/utils"
- "github.com/pkg/errors"
-)
-
-func GetEvents(w http.ResponseWriter, r *http.Request) {
- query := struct {
- Since time.Time `schema:"since"`
- Until time.Time `schema:"until"`
- Filters map[string][]string `schema:"filters"`
- }{}
- if err := decodeQuery(r, &query); err != nil {
- utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- }
-
- var libpodFilters = []string{}
- if _, found := r.URL.Query()["filters"]; found {
- for k, v := range query.Filters {
- libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
- }
- }
-
- libpodEvents, err := getRuntime(r).GetEvents(libpodFilters)
- if err != nil {
- utils.BadRequest(w, "filters", strings.Join(r.URL.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/config.go b/pkg/api/handlers/generic/config.go
deleted file mode 100644
index f715d25eb..000000000
--- a/pkg/api/handlers/generic/config.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package generic
-
-// ContainerCreateResponse is the response struct for creating a container
-type ContainerCreateResponse struct {
- // ID of the container created
- Id string `json:"Id"`
- // Warnings during container creation
- Warnings []string `json:"Warnings"`
-}
diff --git a/pkg/api/handlers/generic/ping.go b/pkg/api/handlers/generic/ping.go
deleted file mode 100644
index 44a67d53f..000000000
--- a/pkg/api/handlers/generic/ping.go
+++ /dev/null
@@ -1,25 +0,0 @@
-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/handler.go b/pkg/api/handlers/handler.go
index d60a5b239..2dd2c886b 100644
--- a/pkg/api/handlers/handler.go
+++ b/pkg/api/handlers/handler.go
@@ -1,47 +1,6 @@
package handlers
-import (
- "net/http"
-
- "github.com/containers/libpod/libpod"
- "github.com/gorilla/mux"
- "github.com/gorilla/schema"
- "github.com/pkg/errors"
+const (
+ DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
+ MinimalApiVersion = "1.24"
)
-
-// Convenience routines to reduce boiler plate in handlers
-
-func 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
deleted file mode 100644
index b4acdc312..000000000
--- a/pkg/api/handlers/images.go
+++ /dev/null
@@ -1,202 +0,0 @@
-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) {
- decoder := r.Context().Value("decoder").(*schema.Decoder)
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
-
- query := struct {
- noPrune bool
- }{
- // This is where you can override the golang default value for one of fields
- }
-
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- return
- }
- muxVars := mux.Vars(r)
- if _, found := muxVars["noprune"]; found {
- if query.noPrune {
- utils.UnSupportedParameter("noprune")
- }
- }
- 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/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index df16843c7..cdc34004f 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -1,60 +1,45 @@
package libpod
import (
- "fmt"
"net/http"
+ "path/filepath"
+ "sort"
"strconv"
+ "time"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
+ "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"
+ "github.com/sirupsen/logrus"
)
-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"]
+ name := utils.GetName(r)
_, err := runtime.LookupContainer(name)
if err != nil {
- utils.ContainerNotFound(w, name, err)
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ utils.ContainerNotFound(w, name, err)
+ }
+ utils.InternalServerError(w, err)
return
+
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
-func RemoveContainer(w http.ResponseWriter, r *http.Request) {
- decoder := r.Context().Value("decoder").(*schema.Decoder)
- query := struct {
- Force bool `schema:"force"`
- Vols bool `schema:"v"`
- }{
- // override any golang type defaults
- }
-
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
- return
- }
- utils.RemoveContainer(w, r, query.Force, query.Vols)
-}
func ListContainers(w http.ResponseWriter, r *http.Request) {
var (
- filters []string
+ filterFuncs []libpod.ContainerFilter
+ pss []ListContainer
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
- Filter map[string][]string `schema:"filter"`
+ Filters map[string][]string `schema:"filters"`
Last int `schema:"last"`
Namespace bool `schema:"namespace"`
Pod bool `schema:"pod"`
@@ -69,6 +54,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
+
runtime := r.Context().Value("runtime").(*libpod.Runtime)
opts := shared.PsOptions{
All: query.All,
@@ -76,20 +62,61 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
Size: query.Size,
Sort: "",
Namespace: query.Namespace,
+ NoTrunc: true,
Pod: query.Pod,
Sync: query.Sync,
}
- if len(query.Filter) > 0 {
- for k, v := range query.Filter {
+
+ all := query.All
+ if len(query.Filters) > 0 {
+ for k, v := range query.Filters {
for _, val := range v {
- filters = append(filters, fmt.Sprintf("%s=%s", k, val))
+ generatedFunc, err := shared.GenerateContainerFilterFuncs(k, val, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filterFuncs = append(filterFuncs, generatedFunc)
}
}
}
- pss, err := shared.GetPsContainerOutput(runtime, opts, filters, 2)
+
+ // Docker thinks that if status is given as an input, then we should override
+ // the all setting and always deal with all containers.
+ if len(query.Filters["status"]) > 0 {
+ all = true
+ }
+ if !all {
+ runningOnly, err := shared.GenerateContainerFilterFuncs("status", define.ContainerStateRunning.String(), runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filterFuncs = append(filterFuncs, runningOnly)
+ }
+
+ cons, err := runtime.GetContainers(filterFuncs...)
if err != nil {
utils.InternalServerError(w, err)
}
+ if query.Last > 0 {
+ // Sort the containers we got
+ sort.Sort(psSortCreateTime{cons})
+ // we should perform the lopping before we start getting
+ // the expensive information on containers
+ if query.Last < len(cons) {
+ cons = cons[len(cons)-query.Last:]
+ }
+ }
+ for _, con := range cons {
+ listCon, err := ListContainerBatch(runtime, con, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ pss = append(pss, listCon)
+
+ }
utils.WriteResponse(w, http.StatusOK, pss)
}
@@ -107,7 +134,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
return
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
container, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -121,39 +148,17 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, data)
}
-func KillContainer(w http.ResponseWriter, r *http.Request) {
- // /{version}/containers/(name)/kill
- _, err := utils.KillContainer(w, r)
- if err != nil {
- return
- }
- // Success
- utils.WriteResponse(w, http.StatusNoContent, "")
-}
-
func WaitContainer(w http.ResponseWriter, r *http.Request) {
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
- utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
}
-func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
- // follow
- // since
- // timestamps
- // tail string
-}
-
-func CreateContainer(w http.ResponseWriter, r *http.Request) {
-
-}
-
func UnmountContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
conn, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -169,7 +174,7 @@ func UnmountContainer(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"]
+ name := utils.GetName(r)
conn, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
@@ -201,3 +206,122 @@ func ShowMountedContainers(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, response)
}
+
+// BatchContainerOp is used in ps to reduce performance hits by "batching"
+// locks.
+func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.PsOptions) (ListContainer, error) {
+ var (
+ conConfig *libpod.ContainerConfig
+ conState define.ContainerStatus
+ err error
+ exitCode int32
+ exited bool
+ pid int
+ size *shared.ContainerSize
+ startedTime time.Time
+ exitedTime time.Time
+ cgroup, ipc, mnt, net, pidns, user, uts string
+ )
+
+ batchErr := ctr.Batch(func(c *libpod.Container) error {
+ conConfig = c.Config()
+ conState, err = c.State()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container state")
+ }
+
+ exitCode, exited, err = c.ExitCode()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container exit code")
+ }
+ startedTime, err = c.StartedTime()
+ if err != nil {
+ logrus.Errorf("error getting started time for %q: %v", c.ID(), err)
+ }
+ exitedTime, err = c.FinishedTime()
+ if err != nil {
+ logrus.Errorf("error getting exited time for %q: %v", c.ID(), err)
+ }
+
+ if !opts.Size && !opts.Namespace {
+ return nil
+ }
+
+ if opts.Namespace {
+ pid, err = c.PID()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container pid")
+ }
+ ctrPID := strconv.Itoa(pid)
+ cgroup, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup"))
+ ipc, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc"))
+ mnt, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt"))
+ net, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net"))
+ pidns, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid"))
+ user, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user"))
+ uts, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts"))
+ }
+ if opts.Size {
+ size = new(shared.ContainerSize)
+
+ rootFsSize, err := c.RootFsSize()
+ if err != nil {
+ logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err)
+ }
+
+ rwSize, err := c.RWSize()
+ if err != nil {
+ logrus.Errorf("error getting rw size for %q: %v", c.ID(), err)
+ }
+
+ size.RootFsSize = rootFsSize
+ size.RwSize = rwSize
+ }
+ return nil
+ })
+
+ if batchErr != nil {
+ return ListContainer{}, batchErr
+ }
+
+ ps := ListContainer{
+ Command: conConfig.Command,
+ Created: conConfig.CreatedTime.Unix(),
+ Exited: exited,
+ ExitCode: exitCode,
+ ExitedAt: exitedTime.Unix(),
+ ID: conConfig.ID,
+ Image: conConfig.RootfsImageName,
+ IsInfra: conConfig.IsInfra,
+ Labels: conConfig.Labels,
+ Mounts: ctr.UserVolumes(),
+ Names: []string{conConfig.Name},
+ Pid: pid,
+ Pod: conConfig.Pod,
+ Ports: conConfig.PortMappings,
+ Size: size,
+ StartedAt: startedTime.Unix(),
+ State: conState.String(),
+ }
+ if opts.Pod && len(conConfig.Pod) > 0 {
+ pod, err := rt.GetPod(conConfig.Pod)
+ if err != nil {
+ return ListContainer{}, err
+ }
+ ps.PodName = pod.Name()
+ }
+
+ if opts.Namespace {
+ ns := ListContainerNamespaces{
+ Cgroup: cgroup,
+ IPC: ipc,
+ MNT: mnt,
+ NET: net,
+ PIDNS: pidns,
+ User: user,
+ UTS: uts,
+ }
+ ps.Namespaces = ns
+ }
+ return ps, nil
+}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
new file mode 100644
index 000000000..ebca41151
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -0,0 +1,29 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
+)
+
+// CreateContainer takes a specgenerator and makes a container. It returns
+// the new container ID on success along with any warnings.
+func CreateContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ var sg specgen.SpecGenerator
+ if err := json.NewDecoder(r.Body).Decode(&sg); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ ctr, err := sg.MakeContainer(runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ response := utils.ContainerCreateResponse{ID: ctr.ID()}
+ utils.WriteJSON(w, http.StatusCreated, response)
+}
diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go
index 0d7bf3ea7..6eb2ab0e3 100644
--- a/pkg/api/handlers/libpod/healthcheck.go
+++ b/pkg/api/handlers/libpod/healthcheck.go
@@ -5,21 +5,39 @@ import (
"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"]
+ name := utils.GetName(r)
status, err := runtime.HealthCheck(name)
if err != nil {
if status == libpod.HealthCheckContainerNotFound {
utils.ContainerNotFound(w, name, err)
+ return
}
+ if status == libpod.HealthCheckNotDefined {
+ utils.Error(w, "no healthcheck defined", http.StatusConflict, err)
+ return
+ }
+ if status == libpod.HealthCheckContainerStopped {
+ utils.Error(w, "container not running", http.StatusConflict, err)
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ ctr, err := runtime.LookupContainer(name)
+ if err != nil {
utils.InternalServerError(w, err)
+ return
}
- utils.WriteResponse(w, http.StatusOK, status)
+
+ hcLog, err := ctr.GetHealthCheckLog()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, hcLog)
}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index bbc8c9346..4b24d7d9f 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -1,15 +1,28 @@
package libpod
import (
+ "context"
"fmt"
+ "io"
"io/ioutil"
"net/http"
"os"
+ "strconv"
+ "strings"
+ "github.com/containers/buildah"
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/docker/reference"
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
- "github.com/gorilla/mux"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -26,11 +39,8 @@ import (
// 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"]
+ name := utils.GetName(r)
_, err := runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
@@ -41,22 +51,39 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
}
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)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+
+ img, 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
+ }
+
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ WhatRequires bool `schema:"whatrequires"`
+ }{
+ WhatRequires: false,
+ }
+ 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
+ }
+
+ tree, err := img.GenerateTree(query.WhatRequires)
+ if err != nil {
+ utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, tree)
}
func GetImage(w http.ResponseWriter, r *http.Request) {
- name := mux.Vars(r)["name"]
- newImage, err := handlers.GetImage(r, name)
+ name := utils.GetName(r)
+ newImage, err := utils.GetImage(r, name)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
return
@@ -67,15 +94,15 @@ func GetImage(w http.ResponseWriter, r *http.Request) {
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))
+ var summaries = make([]*entities.ImageSummary, len(images))
for j, img := range images {
is, err := handlers.ImageToImageSummary(img)
if err != nil {
@@ -83,7 +110,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
return
}
// libpod has additional fields that we need to populate.
- is.CreatedTime = img.Created()
+ is.Created = img.Created().Unix()
is.ReadOnly = img.IsReadOnly()
summaries[j] = is
}
@@ -91,8 +118,9 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
}
func PruneImages(w http.ResponseWriter, r *http.Request) {
- // 200 ok
- // 500 internal
+ var (
+ err error
+ )
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -110,10 +138,21 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
var libpodFilters = []string{}
if _, found := r.URL.Query()["filters"]; found {
+ dangling := query.Filters["all"]
+ if len(dangling) > 0 {
+ query.All, err = strconv.ParseBool(query.Filters["all"][0])
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ }
+ // dangling is special and not implemented in the libpod side of things
+ delete(query.Filters, "dangling")
for k, v := range query.Filters {
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
}
}
+
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
@@ -129,7 +168,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
Compress bool `schema:"compress"`
Format string `schema:"format"`
}{
- // override any golang type defaults
+ Format: "docker-archive",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
@@ -138,11 +177,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
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"))
@@ -152,12 +186,13 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
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
@@ -171,3 +206,299 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
defer os.Remove(tmpfile.Name())
utils.WriteResponse(w, http.StatusOK, rdr)
}
+
+func ImagesLoad(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Reference string `schema:"reference"`
+ }{
+ // Add defaults here once needed.
+ }
+
+ 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
+ }
+
+ tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ defer tmpfile.Close()
+
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+
+ tmpfile.Close()
+ loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image"))
+ return
+ }
+ split := strings.Split(loadedImage, ",")
+ newImage, err := runtime.ImageRuntime().NewFromLocal(split[0])
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // TODO this should go into libpod proper at some point.
+ if len(query.Reference) > 0 {
+ if err := newImage.TagImage(query.Reference); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesLoadReport{ID: loadedImage})
+}
+
+func ImagesImport(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Changes []string `schema:"changes"`
+ Message string `schema:"message"`
+ Reference string `schema:"reference"`
+ URL string `schema:"URL"`
+ }{
+ // Add defaults here once needed.
+ }
+
+ 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
+ }
+
+ // Check if we need to load the image from a URL or from the request's body.
+ source := query.URL
+ if len(query.URL) == 0 {
+ tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ defer tmpfile.Close()
+
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+
+ tmpfile.Close()
+ source = tmpfile.Name()
+ }
+ importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesImportReport{ID: importedImage})
+}
+
+func ImagesPull(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Reference string `schema:"reference"`
+ Credentials string `schema:"credentials"`
+ OverrideOS string `schema:"overrideOS"`
+ OverrideArch string `schema:"overrideArch"`
+ TLSVerify bool `schema:"tlsVerify"`
+ AllTags bool `schema:"allTags"`
+ }{
+ TLSVerify: true,
+ }
+
+ 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.Reference) == 0 {
+ utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
+ return
+ }
+ // Enforce the docker transport. This is just a precaution as some callers
+ // might accustomed to using the "transport:reference" notation. Using
+ // another than the "docker://" transport does not really make sense for a
+ // remote case. For loading tarballs, the load and import endpoints should
+ // be used.
+ imageRef, err := alltransports.ParseImageName(query.Reference)
+ if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("reference %q must be a docker reference", query.Reference))
+ return
+ } else if err != nil {
+ origErr := err
+ imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s://%s", docker.Transport.Name(), query.Reference))
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(origErr, "reference %q must be a docker reference", query.Reference))
+ return
+ }
+ }
+
+ // all-tags doesn't work with a tagged reference, so let's check early
+ namedRef, err := reference.Parse(query.Reference)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "error parsing reference %q", query.Reference))
+ return
+ }
+ if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("reference %q must not have a tag for all-tags", query.Reference))
+ return
+ }
+
+ var registryCreds *types.DockerAuthConfig
+ if len(query.Credentials) != 0 {
+ creds, err := util.ParseRegistryCreds(query.Credentials)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
+ return
+ }
+ registryCreds = creds
+ }
+
+ // Setup the registry options
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ OSChoice: query.OverrideOS,
+ ArchitectureChoice: query.OverrideArch,
+ }
+ if query.TLSVerify {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ // Prepare the images we want to pull
+ imagesToPull := []string{}
+ res := []handlers.LibpodImagesPullReport{}
+ imageName := namedRef.String()
+
+ if !query.AllTags {
+ imagesToPull = append(imagesToPull, imageName)
+ } else {
+ systemContext := image.GetSystemContext("", "", false)
+ tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
+ return
+ }
+ for _, tag := range tags {
+ imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag))
+ }
+ }
+
+ // Finally pull the images
+ for _, img := range imagesToPull {
+ newImage, err := runtime.ImageRuntime().New(
+ context.Background(),
+ img,
+ "",
+ "",
+ os.Stderr,
+ &dockerRegistryOptions,
+ image.SigningOptions{},
+ nil,
+ util.PullImageAlways)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference))
+ return
+ }
+ res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()})
+ }
+
+ utils.WriteResponse(w, http.StatusOK, res)
+}
+
+func CommitContainer(w http.ResponseWriter, r *http.Request) {
+ var (
+ destImage string
+ mimeType string
+ )
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Author string `schema:"author"`
+ Changes []string `schema:"changes"`
+ Comment string `schema:"comment"`
+ Container string `schema:"container"`
+ Format string `schema:"format"`
+ Pause bool `schema:"pause"`
+ Repo string `schema:"repo"`
+ Tag string `schema:"tag"`
+ }{
+ Format: "oci",
+ }
+
+ 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, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config"))
+ return
+ }
+ sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
+ tag := "latest"
+ options := libpod.ContainerCommitOptions{
+ Pause: true,
+ }
+ switch query.Format {
+ case "oci":
+ mimeType = buildah.OCIv1ImageManifest
+ if len(query.Comment) > 0 {
+ utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)"))
+ return
+ }
+ case "docker":
+ mimeType = manifest.DockerV2Schema2MediaType
+ default:
+ utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format))
+ return
+ }
+ options.CommitOptions = buildah.CommitOptions{
+ SignaturePolicyPath: rtc.Engine.SignaturePolicyPath,
+ ReportWriter: os.Stderr,
+ SystemContext: sc,
+ PreferredManifestType: mimeType,
+ }
+
+ if len(query.Tag) > 0 {
+ tag = query.Tag
+ }
+ options.Message = query.Comment
+ options.Author = query.Author
+ options.Pause = query.Pause
+ options.Changes = query.Changes
+ ctr, err := runtime.LookupContainer(query.Container)
+ if err != nil {
+ utils.Error(w, "failed to lookup container", 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
+}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
new file mode 100644
index 000000000..d87ed7eba
--- /dev/null
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -0,0 +1,166 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/buildah/manifests"
+ copy2 "github.com/containers/image/v5/copy"
+ "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/opencontainers/go-digest"
+ "github.com/pkg/errors"
+)
+
+func ManifestCreate(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Name []string `schema:"name"`
+ Image []string `schema:"image"`
+ All bool `schema:"all"`
+ }{
+ // Add defaults here once needed.
+ }
+ 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
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
+ manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manID})
+}
+
+func ManifestInspect(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ data, err := newImage.InspectManifest()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, data)
+}
+
+func ManifestAdd(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ var manifestInput image.ManifestAddOpts
+ if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
+ newID, err := newImage.AddManifest(*sc, manifestInput)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+
+func ManifestRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Digest string `schema:"digest"`
+ }{
+ // Add defaults here once needed.
+ }
+ name := utils.GetName(r)
+ 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
+ }
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ d, err := digest.Parse(query.Digest)
+ if err != nil {
+ utils.Error(w, "invalid digest", http.StatusBadRequest, err)
+ return
+ }
+ newID, err := newImage.RemoveManifest(d)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+func ManifestPush(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ All bool `schema:"all"`
+ Destination string `schema:"destination"`
+ }{
+ // Add defaults here once needed.
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ dest, err := alltransports.ParseImageName(query.Destination)
+ if err != nil {
+ utils.Error(w, "invalid destination parameter", http.StatusBadRequest, errors.Errorf("invalid destination parameter %q", query.Destination))
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
+ opts := manifests.PushOptions{
+ ImageListSelection: copy2.CopySpecificImages,
+ SystemContext: sc,
+ }
+ if query.All {
+ opts.ImageListSelection = copy2.CopyAllImages
+ }
+ newD, err := newImage.PushManifest(dest, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, newD.String())
+}
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
new file mode 100644
index 000000000..e8a92e93e
--- /dev/null
+++ b/pkg/api/handlers/libpod/networks.go
@@ -0,0 +1,85 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/network"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func CreateNetwork(w http.ResponseWriter, r *http.Request) {}
+func ListNetworks(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ configDir := config.Network.NetworkConfigDir
+ if len(configDir) < 1 {
+ configDir = network.CNIConfigDir
+ }
+ networks, err := network.LoadCNIConfsFromDir(configDir)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, networks)
+}
+
+func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
+ // 200 ok
+ // 404 no such
+ // 500 internal
+ 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 := utils.GetName(r)
+ if err := network.RemoveNetwork(name); err != nil {
+ // If the network cannot be found, we return a 404.
+ if errors.Cause(err) == network.ErrNetworkNotFound {
+ utils.Error(w, "Something went wrong", http.StatusNotFound, err)
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "")
+}
+
+func InspectNetwork(w http.ResponseWriter, r *http.Request) {
+ 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 := utils.GetName(r)
+ n, err := network.InspectNetwork(name)
+ if err != nil {
+ // If the network cannot be found, we return a 404.
+ if errors.Cause(err) == network.ErrNetworkNotFound {
+ utils.Error(w, "Something went wrong", http.StatusNotFound, err)
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, n)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 656a75646..7e9c2e2c0 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -4,108 +4,41 @@ 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/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
- "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()"))
+ var psg specgen.PodSpecGenerator
+ if err := json.NewDecoder(r.Body).Decode(&psg); err != nil {
+ utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
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...)
+ pod, err := psg.MakePod(runtime)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ http_code := http.StatusInternalServerError
+ if errors.Cause(err) == define.ErrPodExists {
+ http_code = http.StatusConflict
+ }
+ utils.Error(w, "Something went wrong.", http_code, err)
return
}
- utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
+ utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()})
}
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 map[string][]string `schema:"filters"`
@@ -118,30 +51,17 @@ func Pods(w http.ResponseWriter, r *http.Request) {
return
}
- if _, found := r.URL.Query()["filters"]; found {
- utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
- return
- }
-
- pods, err := runtime.GetAllPods()
+ pods, err := utils.GetPods(w, r)
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)
+ utils.WriteResponse(w, http.StatusOK, pods)
}
func PodInspect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
@@ -156,14 +76,12 @@ func PodInspect(w http.ResponseWriter, r *http.Request) {
}
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)
+ responses map[string]error
+ errs []error
)
query := struct {
Timeout int `schema:"t"`
@@ -176,90 +94,75 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- allContainersStopped := true
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
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()
+ status, err := pod.GetPodStatus()
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)
+ if status != define.PodStateRunning {
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
if query.Timeout > 0 {
- _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout)
+ responses, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout)
} else {
- _, stopError = pod.Stop(r.Context(), false)
+ responses, stopError = pod.Stop(r.Context(), false)
}
if stopError != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ for _, err := range responses {
+ errs = append(errs, err)
+ }
+ report := entities.PodStopReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
func PodStart(w http.ResponseWriter, r *http.Request) {
+ var (
+ errs []error
+ )
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- allContainersRunning := true
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
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()
+ status, err := pod.GetPodStatus()
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)
+ if status == define.PodStateRunning {
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
- if _, err := pod.Start(r.Context()); err != nil {
+ responses, err := pod.Start(r.Context())
+ if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ for _, err := range responses {
+ errs = append(errs, err)
+ }
+ report := entities.PodStartReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
func PodDelete(w http.ResponseWriter, r *http.Request) {
@@ -268,7 +171,7 @@ func PodDelete(w http.ResponseWriter, r *http.Request) {
decoder = r.Context().Value("decoder").(*schema.Decoder)
)
query := struct {
- force bool `schema:"force"`
+ Force bool `schema:"force"`
}{
// override any golang type defaults
}
@@ -278,109 +181,110 @@ func PodDelete(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
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 {
+ 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, "")
+ report := entities.PodRmReport{
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
func PodRestart(w http.ResponseWriter, r *http.Request) {
+ var (
+ errs []error
+ )
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
return
}
- _, err = pod.Restart(r.Context())
+ responses, err := pod.Restart(r.Context())
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ for _, err := range responses {
+ errs = append(errs, err)
+ }
+ report := entities.PodRestartReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
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"))
+ pruned, err := runtime.PrunePods()
+ if err != nil {
+ utils.InternalServerError(w, err)
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, "")
+ utils.WriteResponse(w, http.StatusOK, pruned)
}
func PodPause(w http.ResponseWriter, r *http.Request) {
+ var (
+ errs []error
+ )
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
return
}
- _, err = pod.Pause()
+ responses, err := pod.Pause()
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ for _, v := range responses {
+ errs = append(errs, v)
+ }
+ report := entities.PodPauseReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
func PodUnpause(w http.ResponseWriter, r *http.Request) {
- // 200 ok
- // 404 no such
- // 500 internal
+ var (
+ errs []error
+ )
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
return
}
- _, err = pod.Unpause()
+ responses, err := pod.Unpause()
if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ for _, v := range responses {
+ errs = append(errs, v)
+ }
+ report := entities.PodUnpauseReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, &report)
}
func PodKill(w http.ResponseWriter, r *http.Request) {
@@ -388,9 +292,10 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
runtime = r.Context().Value("runtime").(*libpod.Runtime)
decoder = r.Context().Value("decoder").(*schema.Decoder)
signal = "SIGKILL"
+ errs []error
)
query := struct {
- signal string `schema:"signal"`
+ Signal string `schema:"signal"`
}{
// override any golang type defaults
}
@@ -399,16 +304,15 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- muxVars := mux.Vars(r)
- if _, found := muxVars["signal"]; found {
- signal = query.signal
+ if _, found := r.URL.Query()["signal"]; found {
+ signal = query.Signal
}
sig, err := util.ParseSignal(signal)
if err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "unable to parse signal value"))
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
@@ -431,21 +335,32 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID()))
return
}
- _, err = pod.Kill(uint(sig))
+
+ responses, err := pod.Kill(uint(sig))
if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+
+ for _, v := range responses {
+ if v != nil {
+ errs = append(errs, v)
+ }
+ }
+ report := &entities.PodKillReport{
+ Errs: errs,
+ Id: pod.ID(),
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
}
func PodExists(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
_, err := runtime.LookupPod(name)
if err != nil {
utils.PodNotFound(w, name, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
new file mode 100644
index 000000000..1fad2dd1a
--- /dev/null
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -0,0 +1,94 @@
+package libpod
+
+import (
+ "net/http"
+ "os"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file
+const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml"
+
+// List Containers
+// swagger:response ListContainers
+type swagInspectPodResponse struct {
+ // in:body
+ Body []ListContainer
+}
+
+// Inspect Manifest
+// swagger:response InspectManifest
+type swagInspectManifestResponse struct {
+ // in:body
+ Body manifest.List
+}
+
+// Kill Pod
+// swagger:response PodKillReport
+type swagKillPodResponse struct {
+ // in:body
+ Body entities.PodKillReport
+}
+
+// Pause pod
+// swagger:response PodPauseReport
+type swagPausePodResponse struct {
+ // in:body
+ Body entities.PodPauseReport
+}
+
+// Unpause pod
+// swagger:response PodUnpauseReport
+type swagUnpausePodResponse struct {
+ // in:body
+ Body entities.PodUnpauseReport
+}
+
+// Stop pod
+// swagger:response PodStopReport
+type swagStopPodResponse struct {
+ // in:body
+ Body entities.PodStopReport
+}
+
+// Restart pod
+// swagger:response PodRestartReport
+type swagRestartPodResponse struct {
+ // in:body
+ Body entities.PodRestartReport
+}
+
+// Start pod
+// swagger:response PodStartReport
+type swagStartPodResponse struct {
+ // in:body
+ Body entities.PodStartReport
+}
+
+// Rm pod
+// swagger:response PodRmReport
+type swagRmPodResponse struct {
+ // in:body
+ Body entities.PodRmReport
+}
+
+func ServeSwagger(w http.ResponseWriter, r *http.Request) {
+ path := DefaultPodmanSwaggerSpec
+ if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
+ path = p
+ }
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ utils.InternalServerError(w, errors.Errorf("file %q does not exist", path))
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ w.Header().Set("Content-Type", "text/yaml")
+ http.ServeFile(w, r, path)
+}
diff --git a/pkg/api/handlers/libpod/types.go b/pkg/api/handlers/libpod/types.go
new file mode 100644
index 000000000..0949b2a72
--- /dev/null
+++ b/pkg/api/handlers/libpod/types.go
@@ -0,0 +1,82 @@
+package libpod
+
+import (
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+)
+
+// Listcontainer describes a container suitable for listing
+type ListContainer struct {
+ // Container command
+ Command []string
+ // Container creation time
+ Created int64
+ // If container has exited/stopped
+ Exited bool
+ // Time container exited
+ ExitedAt int64
+ // If container has exited, the return code from the command
+ ExitCode int32
+ // The unique identifier for the container
+ ID string `json:"Id"`
+ // Container image
+ Image string
+ // If this container is a Pod infra container
+ IsInfra bool
+ // Labels for container
+ Labels map[string]string
+ // User volume mounts
+ Mounts []string
+ // The names assigned to the container
+ Names []string
+ // Namespaces the container belongs to. Requires the
+ // namespace boolean to be true
+ Namespaces ListContainerNamespaces
+ // The process id of the container
+ Pid int
+ // If the container is part of Pod, the Pod ID. Requires the pod
+ // boolean to be set
+ Pod string
+ // If the container is part of Pod, the Pod name. Requires the pod
+ // boolean to be set
+ PodName string
+ // Port mappings
+ Ports []ocicni.PortMapping
+ // Size of the container rootfs. Requires the size boolean to be true
+ Size *shared.ContainerSize
+ // Time when container started
+ StartedAt int64
+ // State of container
+ State string
+}
+
+// ListContainer Namespaces contains the identifiers of the container's Linux namespaces
+type ListContainerNamespaces struct {
+ // Mount namespace
+ MNT string `json:"Mnt,omitempty"`
+ // Cgroup namespace
+ Cgroup string `json:"Cgroup,omitempty"`
+ // IPC namespace
+ IPC string `json:"Ipc,omitempty"`
+ // Network namespace
+ NET string `json:"Net,omitempty"`
+ // PID namespace
+ PIDNS string `json:"Pidns,omitempty"`
+ // UTS namespace
+ UTS string `json:"Uts,omitempty"`
+ // User namespace
+ User string `json:"User,omitempty"`
+}
+
+// sortContainers helps us set-up ability to sort by createTime
+type sortContainers []*libpod.Container
+
+func (a sortContainers) Len() int { return len(a) }
+func (a sortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type psSortCreateTime struct{ sortContainers }
+
+func (a psSortCreateTime) Less(i, j int) bool {
+ return a.sortContainers[i].CreatedTime().Before(a.sortContainers[j].CreatedTime())
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index 3e0e597c6..5a6fc021e 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -6,17 +6,15 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
- "github.com/gorilla/mux"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/filters"
"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)
@@ -26,13 +24,12 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
}{
// override any golang type defaults
}
- input := handlers.VolumeCreateConfig{}
+ input := entities.VolumeCreateOptions{}
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()"))
@@ -48,72 +45,124 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
if len(input.Label) > 0 {
volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label))
}
- if len(input.Opts) > 0 {
- parsedOptions, err := shared.ParseVolumeOptions(input.Opts)
+ if len(input.Options) > 0 {
+ parsedOptions, err := shared.ParseVolumeOptions(input.Options)
if err != nil {
utils.InternalServerError(w, err)
+ return
}
volumeOptions = append(volumeOptions, parsedOptions...)
}
vol, err := runtime.NewVolume(r.Context(), volumeOptions...)
if err != nil {
utils.InternalServerError(w, err)
+ return
+ }
+ config, err := vol.Config()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
}
- utils.WriteResponse(w, http.StatusOK, vol.Name())
+ volResponse := entities.VolumeConfigResponse{
+ Name: config.Name,
+ Driver: config.Driver,
+ Mountpoint: config.MountPoint,
+ CreatedAt: config.CreatedTime,
+ Labels: config.Labels,
+ Options: config.Options,
+ UID: config.UID,
+ GID: config.GID,
+ }
+ utils.WriteResponse(w, http.StatusOK, volResponse)
}
func InspectVolume(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
)
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
vol, err := runtime.GetVolume(name)
if err != nil {
utils.VolumeNotFound(w, name, err)
+ return
}
- inspect, err := vol.Inspect()
- if err != nil {
- utils.InternalServerError(w, err)
+ volResponse := entities.VolumeConfigResponse{
+ Name: vol.Name(),
+ Driver: vol.Driver(),
+ Mountpoint: vol.MountPoint(),
+ CreatedAt: vol.CreatedTime(),
+ Labels: vol.Labels(),
+ Scope: vol.Scope(),
+ Options: vol.Options(),
+ UID: vol.UID(),
+ GID: vol.GID(),
}
- utils.WriteResponse(w, http.StatusOK, inspect)
+ utils.WriteResponse(w, http.StatusOK, volResponse)
}
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.
- */
+ var (
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ volumeConfigs []*entities.VolumeListReport
+ )
+ query := struct {
+ Filters map[string][]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
+ }
+
+ volumeFilters, err := filters.GenerateVolumeFilters(query.Filters)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ vols, err := runtime.Volumes(volumeFilters...)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for _, v := range vols {
+ config := entities.VolumeConfigResponse{
+ Name: v.Name(),
+ Driver: v.Driver(),
+ Mountpoint: v.MountPoint(),
+ CreatedAt: v.CreatedTime(),
+ Labels: v.Labels(),
+ Scope: v.Scope(),
+ Options: v.Options(),
+ UID: v.UID(),
+ GID: v.GID(),
+ }
+ volumeConfigs = append(volumeConfigs, &entities.VolumeListReport{VolumeConfigResponse: config})
+ }
+ utils.WriteResponse(w, http.StatusOK, volumeConfigs)
}
func PruneVolumes(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ reports []*entities.VolumePruneReport
)
- 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])
+ pruned, err := runtime.PruneVolumes(r.Context())
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for k, v := range pruned {
+ reports = append(reports, &entities.VolumePruneReport{
+ Err: v,
+ Id: k,
+ })
}
- utils.WriteResponse(w, http.StatusOK, pruned)
+ utils.WriteResponse(w, http.StatusOK, reports)
}
func RemoveVolume(w http.ResponseWriter, r *http.Request) {
@@ -132,13 +181,19 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
- name := mux.Vars(r)["name"]
+ name := utils.GetName(r)
vol, err := runtime.LookupVolume(name)
if err != nil {
utils.VolumeNotFound(w, name, err)
+ return
}
if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil {
+ if errors.Cause(err) == define.ErrVolumeBeingUsed {
+ utils.Error(w, "volumes being used", http.StatusConflict, err)
+ return
+ }
utils.InternalServerError(w, err)
+ return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go
index faae98798..e6e937729 100644
--- a/pkg/api/handlers/swagger.go
+++ b/pkg/api/handlers/swagger.go
@@ -1,9 +1,10 @@
package handlers
import (
- "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/inspect"
"github.com/docker/docker/api/types"
)
@@ -26,6 +27,27 @@ type swagImageInspect struct {
}
}
+// Load response
+// swagger:response DocsLibpodImagesLoadResponse
+type swagLibpodImagesLoadResponse struct {
+ // in:body
+ Body []LibpodImagesLoadReport
+}
+
+// Import response
+// swagger:response DocsLibpodImagesImportResponse
+type swagLibpodImagesImportResponse struct {
+ // in:body
+ Body LibpodImagesImportReport
+}
+
+// Pull response
+// swagger:response DocsLibpodImagesPullResponse
+type swagLibpodImagesPullResponse struct {
+ // in:body
+ Body LibpodImagesPullReport
+}
+
// Delete response
// swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct {
@@ -83,19 +105,12 @@ type swagDockerTopResponse struct {
}
}
-// List containers
-// swagger:response LibpodListContainersResponse
-type swagLibpodListContainersResponse struct {
- // in:body
- Body []shared.PsContainerOutput
-}
-
// Inspect container
// swagger:response LibpodInspectContainerResponse
type swagLibpodInspectContainerResponse struct {
// in:body
Body struct {
- libpod.InspectContainerData
+ define.InspectContainerData
}
}
@@ -103,7 +118,7 @@ type swagLibpodInspectContainerResponse struct {
// swagger:response ListPodsResponse
type swagListPodsResponse struct {
// in:body
- Body []libpod.PodInspect
+ Body []entities.ListPodsReport
}
// Inspect pod
@@ -123,3 +138,12 @@ type swagInspectVolumeResponse struct {
libpod.InspectVolumeData
}
}
+
+// Image tree response
+// swagger:response LibpodImageTreeResponse
+type swagImageTreeResponse struct {
+ // in:body
+ Body struct {
+ ImageTreeResponse
+ }
+}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 33cd51164..1ca5db3f9 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -13,6 +13,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
libpodImage "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/domain/entities"
docker "github.com/docker/docker/api/types"
dockerContainer "github.com/docker/docker/api/types/container"
dockerEvents "github.com/docker/docker/api/types/events"
@@ -33,10 +34,16 @@ type ContainerConfig struct {
dockerContainer.Config
}
-type ImageSummary struct {
- docker.ImageSummary
- CreatedTime time.Time `json:"CreatedTime,omitempty"`
- ReadOnly bool `json:"ReadOnly,omitempty"`
+type LibpodImagesLoadReport struct {
+ ID string `json:"id"`
+}
+
+type LibpodImagesImportReport struct {
+ ID string `json:"id"`
+}
+
+type LibpodImagesPullReport struct {
+ ID string `json:"id"`
}
type ContainersPruneReport struct {
@@ -66,14 +73,6 @@ type Container struct {
docker.ContainerCreateConfig
}
-type ContainerStats struct {
- docker.ContainerStats
-}
-
-type Ping struct {
- docker.Ping
-}
-
type Version struct {
docker.Version
}
@@ -124,37 +123,16 @@ type CreateContainerConfig struct {
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"`
-}
-
+// swagger:model IDResponse
type IDResponse struct {
+ // ID
ID string `json:"id"`
}
-type Stats struct {
- docker.StatsJSON
-}
-
type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
-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"`
}
@@ -181,6 +159,14 @@ type ImageTreeResponse struct {
Layers []ImageLayer `json:"layers"`
}
+type ExecCreateConfig struct {
+ docker.ExecConfig
+}
+
+type ExecCreateResponse struct {
+ docker.IDResponse
+}
+
func EventToApiEvent(e *events.Event) *Event {
return &Event{dockerEvents.Message{
Type: e.Type.String(),
@@ -199,23 +185,13 @@ func EventToApiEvent(e *events.Event) *Event {
}}
}
-func ImageToImageSummary(l *libpodImage.Image) (*ImageSummary, error) {
+func ImageToImageSummary(l *libpodImage.Image) (*entities.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 {
@@ -231,20 +207,43 @@ func ImageToImageSummary(l *libpodImage.Image) (*ImageSummary, error) {
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,
+
+ repoTags, err := l.RepoTags()
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain RepoTags for image %s", l.ID())
+ }
+
+ history, err := l.History(context.TODO())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to obtain History for image %s", l.ID())
+ }
+ historyIds := make([]string, len(history))
+ for i, h := range history {
+ historyIds[i] = h.ID
+ }
+
+ digests := make([]string, len(l.Digests()))
+ for i, d := range l.Digests() {
+ digests[i] = string(d)
+ }
+
+ is := entities.ImageSummary{
+ ID: l.ID(),
+ ParentId: l.Parent,
+ RepoTags: repoTags,
+ Created: l.Created().Unix(),
+ Size: int64(*size),
+ SharedSize: 0,
+ VirtualSize: l.VirtualSize,
+ Labels: labels,
+ Containers: containerCount,
+ ReadOnly: l.IsReadOnly(),
+ Dangling: l.Dangling(),
+ Names: l.Names(),
+ Digest: string(l.Digest()),
+ Digests: digests,
+ ConfigDigest: string(l.ConfigDigest),
+ History: historyIds,
}
return &is, nil
}
@@ -341,35 +340,45 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
}
-func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Container, error) {
+func LibpodToContainer(l *libpod.Container, infoData []define.InfoData, sz bool) (*Container, error) {
imageId, imageName := l.Image()
- sizeRW, err := l.RWSize()
- if err != nil {
- return nil, err
- }
- SizeRootFs, err := l.RootFsSize()
- if err != nil {
+ var (
+ err error
+ sizeRootFs int64
+ sizeRW int64
+ state define.ContainerStatus
+ )
+
+ if state, err = l.State(); err != nil {
return nil, err
}
+ stateStr := state.String()
+ if stateStr == "configured" {
+ stateStr = "created"
+ }
- state, err := l.State()
- if err != nil {
- return nil, err
+ if sz {
+ if sizeRW, err = l.RWSize(); err != nil {
+ return nil, err
+ }
+ if sizeRootFs, err = l.RootFsSize(); err != nil {
+ return nil, err
+ }
}
return &Container{docker.Container{
ID: l.ID(),
- Names: []string{l.Name()},
+ Names: []string{fmt.Sprintf("/%s", l.Name())},
Image: imageName,
ImageID: imageId,
Command: strings.Join(l.Command(), " "),
Created: l.CreatedTime().Unix(),
Ports: nil,
SizeRw: sizeRW,
- SizeRootFs: SizeRootFs,
+ SizeRootFs: sizeRootFs,
Labels: l.Labels(),
- State: string(state),
+ State: stateStr,
Status: "",
HostConfig: struct {
NetworkMode string `json:",omitempty"`
@@ -382,9 +391,9 @@ func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Contai
}, nil
}
-func LibpodToContainerJSON(l *libpod.Container) (*docker.ContainerJSON, error) {
+func LibpodToContainerJSON(l *libpod.Container, sz bool) (*docker.ContainerJSON, error) {
_, imageName := l.Image()
- inspect, err := l.Inspect(true)
+ inspect, err := l.Inspect(sz)
if err != nil {
return nil, err
}
@@ -431,7 +440,7 @@ func LibpodToContainerJSON(l *libpod.Container) (*docker.ContainerJSON, error) {
HostsPath: inspect.HostsPath,
LogPath: l.LogPath(),
Node: nil,
- Name: l.Name(),
+ Name: fmt.Sprintf("/%s", l.Name()),
RestartCount: 0,
Driver: inspect.Driver,
Platform: "linux",
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index 2c986db3a..bbe4cee3c 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -1,77 +1,33 @@
package utils
import (
- "fmt"
+ "context"
"net/http"
- "syscall"
"time"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/gorilla/mux"
+ createconfig "github.com/containers/libpod/pkg/spec"
"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, "")
+// ContainerCreateResponse is the response struct for creating a container
+type ContainerCreateResponse struct {
+ // ID of the container created
+ ID string `json:"Id"`
+ // Warnings during container creation
+ Warnings []string `json:"Warnings"`
}
func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
+ var (
+ err error
+ interval time.Duration
+ )
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"`
@@ -82,25 +38,34 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
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 {
- UnSupportedParameter("condition")
+ if _, found := r.URL.Query()["interval"]; found {
+ interval, err = time.ParseDuration(query.Interval)
+ if err != nil {
+ InternalServerError(w, err)
+ return 0, err
+ }
+ } else {
+ interval, err = time.ParseDuration("250ms")
+ if err != nil {
+ InternalServerError(w, err)
+ return 0, err
+ }
}
-
- name := mux.Vars(r)["name"]
+ condition := define.ContainerStateStopped
+ if _, found := r.URL.Query()["condition"]; found {
+ condition, err = define.StringToContainerStatus(query.Condition)
+ if err != nil {
+ InternalServerError(w, err)
+ return 0, err
+ }
+ }
+ name := GetName(r)
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()
+ return con.WaitForConditionWithInterval(interval, condition)
}
// GenerateFilterFuncsFromMap is used to generate un-executed functions that can be used to filter
@@ -120,3 +85,18 @@ func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string)
}
return filterFuncs, nil
}
+
+func CreateContainer(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, cc *createconfig.CreateConfig) {
+ var pod *libpod.Pod
+ ctr, err := shared.CreateContainerFromCreateConfig(runtime, cc, ctx, pod)
+ if err != nil {
+ Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
+ return
+ }
+
+ response := ContainerCreateResponse{
+ ID: ctr.ID(),
+ Warnings: []string{}}
+
+ WriteResponse(w, http.StatusCreated, response)
+}
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index 9d2081cd8..8d499f40b 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -21,8 +21,9 @@ 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(),
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ ResponseCode: code,
}
WriteJSON(w, code, em)
}
@@ -79,6 +80,8 @@ type ErrorModel struct {
// human error message, formatted for a human to read
// example: human error message
Message string `json:"message"`
+ // http response code
+ ResponseCode int `json:"response"`
}
func (e ErrorModel) Error() string {
@@ -89,6 +92,10 @@ func (e ErrorModel) Cause() error {
return errors.New(e.Because)
}
+func (e ErrorModel) Code() int {
+ return e.ResponseCode
+}
+
// UnsupportedParameter logs a given param by its string name as not supported.
func UnSupportedParameter(param string) {
log.Infof("API parameter %q: not supported", param)
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index f2ce26f1a..32b8c5b0a 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -5,9 +5,12 @@ import (
"fmt"
"io"
"net/http"
+ "net/url"
"os"
"strings"
+ "github.com/gorilla/mux"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -20,6 +23,14 @@ func IsLibpodRequest(r *http.Request) bool {
// WriteResponse encodes the given value as JSON or string and renders it for http client
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
+ // RFC2616 explicitly states that the following status codes "MUST NOT
+ // include a message-body":
+ switch code {
+ case http.StatusNoContent, http.StatusNotModified: // 204, 304
+ w.WriteHeader(code)
+ return
+ }
+
switch v := value.(type) {
case string:
w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
@@ -59,3 +70,18 @@ func FilterMapToString(filters map[string][]string) (string, error) {
}
return string(f), nil
}
+
+func getVar(r *http.Request, k string) string {
+ val := mux.Vars(r)[k]
+ safeVal, err := url.PathUnescape(val)
+ if err != nil {
+ logrus.Error(errors.Wrapf(err, "failed to unescape mux key %s, value %s", k, val))
+ return val
+ }
+ return safeVal
+}
+
+// GetName extracts the name from the mux
+func GetName(r *http.Request) string {
+ return getVar(r, "name")
+}
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
index a0d340471..696d5f745 100644
--- a/pkg/api/handlers/utils/images.go
+++ b/pkg/api/handlers/utils/images.go
@@ -15,19 +15,36 @@ 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
+ All bool
Filters map[string][]string `schema:"filters"`
- // digests bool # digests is currently unused
+ Digests bool
}{
// This is where you can override the golang default value for one of fields
}
+ // TODO I think all is implemented with a filter?
+
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
return nil, err
}
-
var filters = []string{}
- if _, found := r.URL.Query()["filters"]; found {
- filters = append(filters, fmt.Sprintf("reference=%s", ""))
+ if _, found := r.URL.Query()["digests"]; found && query.Digests {
+ UnSupportedParameter("digests")
+ }
+
+ if len(query.Filters) > 0 {
+ for k, v := range query.Filters {
+ for _, val := range v {
+ filters = append(filters, fmt.Sprintf("%s=%s", k, val))
+ }
+ }
+ return runtime.ImageRuntime().GetImagesWithFilters(filters)
+ } else {
+ return runtime.ImageRuntime().GetImages()
}
- return runtime.ImageRuntime().GetImagesWithFilters(filters)
+
+}
+
+func GetImage(r *http.Request, name string) (*image.Image, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ return runtime.ImageRuntime().NewFromLocal(name)
}
diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go
new file mode 100644
index 000000000..79d1a5090
--- /dev/null
+++ b/pkg/api/handlers/utils/pods.go
@@ -0,0 +1,84 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/gorilla/schema"
+)
+
+func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport, error) {
+ var (
+ lps []*entities.ListPodsReport
+ pods []*libpod.Pod
+ podErr error
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ All bool
+ Filters map[string][]string `schema:"filters"`
+ Digests bool
+ }{}
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ return nil, err
+ }
+ var filters = []string{}
+ if _, found := r.URL.Query()["digests"]; found && query.Digests {
+ UnSupportedParameter("digests")
+ }
+
+ if len(query.Filters) > 0 {
+ for k, v := range query.Filters {
+ for _, val := range v {
+ filters = append(filters, fmt.Sprintf("%s=%s", k, val))
+ }
+ }
+ filterFuncs, err := shared.GenerateFilterFunction(runtime, filters)
+ if err != nil {
+ return nil, err
+ }
+ pods, podErr = shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...)
+ } else {
+ pods, podErr = runtime.GetAllPods()
+ }
+ if podErr != nil {
+ return nil, podErr
+ }
+ for _, pod := range pods {
+ status, err := pod.GetPodStatus()
+ if err != nil {
+ return nil, err
+ }
+ ctrs, err := pod.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ lp := entities.ListPodsReport{
+ Cgroup: pod.CgroupParent(),
+ Created: pod.CreatedTime(),
+ Id: pod.ID(),
+ Name: pod.Name(),
+ Namespace: pod.Namespace(),
+ Status: status,
+ }
+ for _, ctr := range ctrs {
+ state, err := ctr.State()
+ if err != nil {
+ return nil, err
+ }
+ lp.Containers = append(lp.Containers, &entities.ListPodContainer{
+ Id: ctr.ID(),
+ Names: ctr.Name(),
+ Status: state.String(),
+ })
+ }
+ lps = append(lps, &lp)
+ }
+ return lps, nil
+}