aboutsummaryrefslogtreecommitdiff
path: root/pkg/api/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r--pkg/api/handlers/compat/containers.go27
-rw-r--r--pkg/api/handlers/compat/containers_archive.go12
-rw-r--r--pkg/api/handlers/compat/containers_attach.go51
-rw-r--r--pkg/api/handlers/compat/containers_start.go9
-rw-r--r--pkg/api/handlers/compat/containers_stats.go6
-rw-r--r--pkg/api/handlers/compat/containers_stop.go (renamed from pkg/api/handlers/compat/container_start.go)0
-rw-r--r--pkg/api/handlers/compat/events.go12
-rw-r--r--pkg/api/handlers/compat/exec.go72
-rw-r--r--pkg/api/handlers/compat/images.go30
-rw-r--r--pkg/api/handlers/compat/images_build.go1
-rw-r--r--pkg/api/handlers/compat/images_push.go15
-rw-r--r--pkg/api/handlers/compat/networks.go301
-rw-r--r--pkg/api/handlers/compat/ping.go8
-rw-r--r--pkg/api/handlers/compat/resize.go86
-rw-r--r--pkg/api/handlers/compat/swagger.go28
-rw-r--r--pkg/api/handlers/compat/types.go2
-rw-r--r--pkg/api/handlers/compat/version.go43
-rw-r--r--pkg/api/handlers/handler.go6
-rw-r--r--pkg/api/handlers/libpod/containers.go4
-rw-r--r--pkg/api/handlers/libpod/containers_create.go3
-rw-r--r--pkg/api/handlers/libpod/copy.go12
-rw-r--r--pkg/api/handlers/libpod/generate.go38
-rw-r--r--pkg/api/handlers/libpod/healthcheck.go24
-rw-r--r--pkg/api/handlers/libpod/images.go128
-rw-r--r--pkg/api/handlers/libpod/manifests.go5
-rw-r--r--pkg/api/handlers/libpod/networks.go68
-rw-r--r--pkg/api/handlers/libpod/play.go82
-rw-r--r--pkg/api/handlers/libpod/swagger.go28
-rw-r--r--pkg/api/handlers/libpod/system.go13
-rw-r--r--pkg/api/handlers/swagger/swagger.go7
-rw-r--r--pkg/api/handlers/types.go13
-rw-r--r--pkg/api/handlers/utils/errors.go8
-rw-r--r--pkg/api/handlers/utils/handler.go86
-rw-r--r--pkg/api/handlers/utils/handler_test.go139
-rw-r--r--pkg/api/handlers/utils/images.go33
35 files changed, 1195 insertions, 205 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 239e41af4..cea4bd0f6 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
+ "io"
"net/http"
"strconv"
"strings"
@@ -295,7 +296,9 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}()
w.WriteHeader(http.StatusOK)
- var builder strings.Builder
+
+ var frame strings.Builder
+ header := make([]byte, 8)
for ok := true; ok; ok = query.Follow {
for line := range logChannel {
if _, found := r.URL.Query()["until"]; found {
@@ -304,10 +307,8 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
}
- // Reset variables we're ready to loop again
- builder.Reset()
- header := [8]byte{}
-
+ // Reset buffer we're ready to loop again
+ frame.Reset()
switch line.Device {
case "stdout":
if !query.Stdout {
@@ -327,17 +328,17 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
if query.Timestamps {
- builder.WriteString(line.Time.Format(time.RFC3339))
- builder.WriteRune(' ')
+ frame.WriteString(line.Time.Format(time.RFC3339))
+ frame.WriteString(" ")
}
- 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 {
+ frame.WriteString(line.Msg)
+
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
log.Errorf("unable to write log output header: %q", err)
}
- if _, err := fmt.Fprint(w, builder.String()); err != nil {
- log.Errorf("unable to write builder string: %q", err)
+ if _, err := io.WriteString(w, frame.String()); err != nil {
+ log.Errorf("unable to write frame string: %q", err)
}
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
new file mode 100644
index 000000000..c3a26873e
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -0,0 +1,12 @@
+package compat
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+func Archive(w http.ResponseWriter, r *http.Request) {
+ utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+}
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go
index 80ad52aee..990140ee1 100644
--- a/pkg/api/handlers/compat/containers_attach.go
+++ b/pkg/api/handlers/compat/containers_attach.go
@@ -10,9 +10,14 @@ import (
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "k8s.io/client-go/tools/remotecommand"
)
+// AttachHeader is the literal header sent for upgraded/hijacked connections for
+// attach, sourced from Docker at:
+// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
+// Using literally to ensure compatibility with existing clients.
+const AttachHeader = "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n"
+
func AttachContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -85,11 +90,11 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
// For Docker compatibility, we need to re-initialize containers in these states.
if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
if err := ctr.Init(r.Context()); err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
+ utils.Error(w, "Container in wrong state", http.StatusConflict, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
return
}
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
- utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers"))
+ utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers - currently in state %s", state.String()))
return
}
@@ -106,10 +111,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
- // This header string sourced from Docker:
- // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
- // Using literally to ensure compatability with existing clients.
- fmt.Fprintf(connection, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
+ fmt.Fprintf(connection, AttachHeader)
logrus.Debugf("Hijack for attach of container %s successful", ctr.ID())
@@ -124,38 +126,3 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
}
-
-func ResizeContainer(w http.ResponseWriter, r *http.Request) {
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
- decoder := r.Context().Value("decoder").(*schema.Decoder)
-
- query := struct {
- Height uint16 `schema:"h"`
- Width uint16 `schema:"w"`
- }{}
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- // This is not a 400, despite the fact that is should be, for
- // compatibility reasons.
- utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options"))
- return
- }
-
- name := utils.GetName(r)
- ctr, err := runtime.LookupContainer(name)
- if err != nil {
- utils.ContainerNotFound(w, name, err)
- return
- }
-
- newSize := remotecommand.TerminalSize{
- Width: query.Width,
- Height: query.Height,
- }
- if err := ctr.AttachResize(newSize); err != nil {
- utils.InternalServerError(w, err)
- return
- }
- // This is not a 204, even though we write nothing, for compatibility
- // reasons.
- utils.WriteResponse(w, http.StatusOK, "")
-}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
index 67bd287ab..cdbc8ff76 100644
--- a/pkg/api/handlers/compat/containers_start.go
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -3,11 +3,12 @@ package compat
import (
"net/http"
+ "github.com/sirupsen/logrus"
+
"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) {
@@ -23,8 +24,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
}
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
+ logrus.Info("the detach keys parameter is not supported on start container")
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
@@ -33,7 +33,6 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.ContainerNotFound(w, name, err)
return
}
-
state, err := con.State()
if err != nil {
utils.InternalServerError(w, err)
@@ -43,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusNotModified, "")
return
}
- if err := con.Start(r.Context(), false); err != nil {
+ if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil {
utils.InternalServerError(w, err)
return
}
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index 53ad0a632..048321add 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -45,12 +45,12 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- if state != define.ContainerStateRunning && !query.Stream {
- utils.InternalServerError(w, define.ErrCtrStateInvalid)
+ if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid)
return
}
- stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
+ stats, err := ctnr.GetContainerStats(&define.ContainerStats{})
if err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
return
diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/containers_stop.go
index d26ef2c82..d26ef2c82 100644
--- a/pkg/api/handlers/compat/container_start.go
+++ b/pkg/api/handlers/compat/containers_stop.go
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 7ebfb0d1e..577ddd0a1 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -26,7 +26,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
Since string `schema:"since"`
Until string `schema:"until"`
Filters map[string][]string `schema:"filters"`
- }{}
+ Stream bool `schema:"stream"`
+ }{
+ Stream: true,
+ }
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()))
}
@@ -41,9 +44,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
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}
+ readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until}
eventsError = runtime.Events(readOpts)
}()
if eventsError != nil {
@@ -55,7 +59,9 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
// If client disappears we need to stop listening for events
go func(done <-chan struct{}) {
<-done
- close(eventChannel)
+ if _, ok := <-eventChannel; ok {
+ close(eventChannel)
+ }
}(r.Context().Done())
// Headers need to be written out before turning Writer() over to json encoder
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go
index ec1a8ac96..6865a3319 100644
--- a/pkg/api/handlers/compat/exec.go
+++ b/pkg/api/handlers/compat/exec.go
@@ -104,4 +104,76 @@ func ExecInspectHandler(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, inspectOut)
+
+ // Only for the Compat API: we want to remove sessions that were
+ // stopped. This is very hacky, but should suffice for now.
+ if !utils.IsLibpodRequest(r) && inspectOut.CanRemove {
+ logrus.Infof("Pruning stale exec session %s from container %s", sessionID, sessionCtr.ID())
+ if err := sessionCtr.ExecRemove(sessionID, false); err != nil && errors.Cause(err) != define.ErrNoSuchExecSession {
+ logrus.Errorf("Error removing stale exec session %s from container %s: %v", sessionID, sessionCtr.ID(), err)
+ }
+ }
+}
+
+// ExecStartHandler runs a given exec session.
+func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ sessionID := mux.Vars(r)["id"]
+
+ // TODO: We should read/support Tty and Detach from here.
+ bodyParams := new(handlers.ExecStartConfig)
+
+ if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String()))
+ return
+ }
+ if bodyParams.Detach {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("Detached exec is not yet supported"))
+ return
+ }
+ // TODO: Verify TTY setting against what inspect session was made with
+
+ 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("Starting exec session %s of container %s", sessionID, sessionCtr.ID())
+
+ state, err := sessionCtr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning {
+ utils.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict, errors.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String()))
+ return
+ }
+
+ // Hijack the connection
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ utils.InternalServerError(w, errors.Errorf("unable to hijack connection"))
+ return
+ }
+
+ connection, buffer, err := hijacker.Hijack()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection"))
+ return
+ }
+
+ fmt.Fprintf(connection, AttachHeader)
+
+ logrus.Debugf("Hijack for attach of container %s exec session %s successful", sessionCtr.ID(), sessionID)
+
+ if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, connection, buffer, nil, nil, nil); err != nil {
+ logrus.Errorf("Error attaching to container %s exec session %s: %v", sessionCtr.ID(), sessionID, err)
+ }
+
+ logrus.Debugf("Attach for container %s exec session %s completed successfully", sessionCtr.ID(), sessionID)
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index ea9cbd691..b64ed0036 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -15,6 +15,7 @@ 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/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
@@ -251,19 +252,32 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
return
}
- /*
- fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
- repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
- tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
- */
fromImage := query.FromImage
if len(query.Tag) >= 1 {
fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
}
- // TODO
- // We are eating the output right now because we haven't talked about how to deal with multiple responses yet
- img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf}
+ if sys := runtime.SystemContext(); sys != nil {
+ registryOpts.DockerCertPath = sys.DockerCertPath
+ }
+ img, err := runtime.ImageRuntime().New(r.Context(),
+ fromImage,
+ "", // signature policy
+ authfile,
+ nil, // writer
+ &registryOpts,
+ image2.SigningOptions{},
+ nil, // label
+ util.PullImageMissing,
+ )
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index e208e6ddc..e9d8fd719 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -226,6 +226,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile)
if err != nil {
utils.InternalServerError(w, err)
+ return
}
// Find image ID that was built...
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index 2260d5557..47976b7c9 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -48,13 +49,17 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- // TODO: the X-Registry-Auth header is not checked yet here nor in any other
- // endpoint. Pushing does NOT work with authentication at the moment.
- dockerRegistryOptions := &image.DockerRegistryOptions{}
- authfile := ""
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ dockerRegistryOptions := &image.DockerRegistryOptions{DockerRegistryCreds: authConf}
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
+ dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
err = newImage.PushImageToHeuristicDestination(
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
new file mode 100644
index 000000000..c52ca093f
--- /dev/null
+++ b/pkg/api/handlers/compat/networks.go
@@ -0,0 +1,301 @@
+package compat
+
+import (
+ "encoding/json"
+ "net"
+ "net/http"
+ "os"
+ "syscall"
+ "time"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/containers/libpod/pkg/network"
+ "github.com/docker/docker/api/types"
+ dockerNetwork "github.com/docker/docker/api/types/network"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+type CompatInspectNetwork struct {
+ types.NetworkResource
+}
+
+func InspectNetwork(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // FYI scope and version are currently unused but are described by the API
+ // Leaving this for if/when we have to enable these
+ //query := struct {
+ // scope string
+ // verbose bool
+ //}{
+ // // override any golang type defaults
+ //}
+ //decoder := r.Context().Value("decoder").(*schema.Decoder)
+ //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
+ //}
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ name := utils.GetName(r)
+ _, err = network.InspectNetwork(config, name)
+ if err != nil {
+ // TODO our network package does not distinguish between not finding a
+ // specific network vs not being able to read it
+ utils.InternalServerError(w, err)
+ return
+ }
+ report, err := getNetworkResourceByName(name, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
+ var (
+ ipamConfigs []dockerNetwork.IPAMConfig
+ )
+ config, err := runtime.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ containerEndpoints := map[string]types.EndpointResource{}
+ // Get the network path so we can get created time
+ networkConfigPath, err := network.GetCNIConfigPathByName(config, name)
+ if err != nil {
+ return nil, err
+ }
+ f, err := os.Stat(networkConfigPath)
+ if err != nil {
+ return nil, err
+ }
+ stat := f.Sys().(*syscall.Stat_t)
+ cons, err := runtime.GetAllContainers()
+ if err != nil {
+ return nil, err
+ }
+ conf, err := libcni.ConfListFromFile(networkConfigPath)
+ if err != nil {
+ return nil, err
+ }
+
+ // No Bridge plugin means we bail
+ bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
+ if err != nil {
+ return nil, err
+ }
+ for _, outer := range bridge.IPAM.Ranges {
+ for _, n := range outer {
+ ipamConfig := dockerNetwork.IPAMConfig{
+ Subnet: n.Subnet,
+ Gateway: n.Gateway,
+ }
+ ipamConfigs = append(ipamConfigs, ipamConfig)
+ }
+ }
+
+ for _, con := range cons {
+ data, err := con.Inspect(false)
+ if err != nil {
+ return nil, err
+ }
+ if netData, ok := data.NetworkSettings.Networks[name]; ok {
+ containerEndpoint := types.EndpointResource{
+ Name: netData.NetworkID,
+ EndpointID: netData.EndpointID,
+ MacAddress: netData.MacAddress,
+ IPv4Address: netData.IPAddress,
+ IPv6Address: netData.GlobalIPv6Address,
+ }
+ containerEndpoints[con.ID()] = containerEndpoint
+ }
+ }
+ report := types.NetworkResource{
+ Name: name,
+ ID: "",
+ Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
+ Scope: "",
+ Driver: network.DefaultNetworkDriver,
+ EnableIPv6: false,
+ IPAM: dockerNetwork.IPAM{
+ Driver: "default",
+ Options: nil,
+ Config: ipamConfigs,
+ },
+ Internal: false,
+ Attachable: false,
+ Ingress: false,
+ ConfigFrom: dockerNetwork.ConfigReference{},
+ ConfigOnly: false,
+ Containers: containerEndpoints,
+ Options: nil,
+ Labels: nil,
+ Peers: nil,
+ Services: nil,
+ }
+ return &report, nil
+}
+
+func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) {
+ var bridge network.HostLocalBridge
+ generic, err := findPluginByName(plugins, pluginType)
+ if err != nil {
+ return bridge, err
+ }
+ err = json.Unmarshal(generic, &bridge)
+ return bridge, err
+}
+
+func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) {
+ for _, p := range plugins {
+ if pluginType == p.Network.Type {
+ return p.Bytes, nil
+ }
+ }
+ return nil, errors.New("unable to find bridge plugin")
+}
+
+func ListNetworks(w http.ResponseWriter, r *http.Request) {
+ var (
+ reports []*types.NetworkResource
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ 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, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // TODO remove when filters are implemented
+ if len(query.Filters) > 0 {
+ utils.InternalServerError(w, errors.New("filters for listing networks is not implemented"))
+ return
+ }
+ netNames, err := network.GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for _, name := range netNames {
+ report, err := getNetworkResourceByName(name, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ reports = append(reports, report)
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func CreateNetwork(w http.ResponseWriter, r *http.Request) {
+ var (
+ name string
+ networkCreate types.NetworkCreateRequest
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+
+ if len(networkCreate.Name) > 0 {
+ name = networkCreate.Name
+ }
+ // At present I think we should just suport the bridge driver
+ // and allow demand to make us consider more
+ if networkCreate.Driver != network.DefaultNetworkDriver {
+ utils.InternalServerError(w, errors.New("network create only supports the bridge driver"))
+ return
+ }
+ ncOptions := entities.NetworkCreateOptions{
+ Driver: network.DefaultNetworkDriver,
+ Internal: networkCreate.Internal,
+ }
+ if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
+ if len(networkCreate.IPAM.Config) > 1 {
+ utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
+ return
+ }
+
+ if len(networkCreate.IPAM.Config[0].Subnet) > 0 {
+ _, subnet, err := net.ParseCIDR(networkCreate.IPAM.Config[0].Subnet)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ncOptions.Subnet = *subnet
+ }
+ if len(networkCreate.IPAM.Config[0].Gateway) > 0 {
+ ncOptions.Gateway = net.ParseIP(networkCreate.IPAM.Config[0].Gateway)
+ }
+ if len(networkCreate.IPAM.Config[0].IPRange) > 0 {
+ _, IPRange, err := net.ParseCIDR(networkCreate.IPAM.Config[0].IPRange)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ncOptions.Range = *IPRange
+ }
+ }
+ ce := abi.ContainerEngine{Libpod: runtime}
+ _, err := ce.NetworkCreate(r.Context(), name, ncOptions)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ report := types.NetworkCreate{
+ CheckDuplicate: networkCreate.CheckDuplicate,
+ Driver: networkCreate.Driver,
+ Scope: networkCreate.Scope,
+ EnableIPv6: networkCreate.EnableIPv6,
+ IPAM: networkCreate.IPAM,
+ Internal: networkCreate.Internal,
+ Attachable: networkCreate.Attachable,
+ Ingress: networkCreate.Ingress,
+ ConfigOnly: networkCreate.ConfigOnly,
+ ConfigFrom: networkCreate.ConfigFrom,
+ Options: networkCreate.Options,
+ Labels: networkCreate.Labels,
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func RemoveNetwork(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
+ }
+ name := utils.GetName(r)
+ exists, err := network.Exists(config, name)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if !exists {
+ utils.Error(w, "network not found", http.StatusNotFound, err)
+ return
+ }
+ if err := network.RemoveNetwork(config, name); err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index 6e77e270f..abee3d8e8 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -5,22 +5,22 @@ import (
"net/http"
"github.com/containers/buildah"
- "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
)
// 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.
+// Note: Additionally handler supports GET and HEAD methods
func Ping(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("API-Version", utils.ApiVersion[utils.CompatTree][utils.CurrentApiVersion].String())
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-API-Version", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String())
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
w.WriteHeader(http.StatusOK)
diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go
new file mode 100644
index 000000000..231b53175
--- /dev/null
+++ b/pkg/api/handlers/compat/resize.go
@@ -0,0 +1,86 @@
+package compat
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+func ResizeTTY(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ // /containers/{id}/resize
+ query := struct {
+ height uint16 `schema:"h"`
+ width uint16 `schema:"w"`
+ }{
+ // 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
+ }
+
+ sz := remotecommand.TerminalSize{
+ Width: query.width,
+ Height: query.height,
+ }
+
+ var status int
+ name := utils.GetName(r)
+ switch {
+ case strings.Contains(r.URL.Path, "/containers/"):
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ if state, err := ctnr.State(); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state"))
+ return
+ } else if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running", http.StatusConflict,
+ fmt.Errorf("container %q in wrong state %q", name, state.String()))
+ return
+ }
+ if err := ctnr.AttachResize(sz); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
+ return
+ }
+ // This is not a 204, even though we write nothing, for compatibility
+ // reasons.
+ status = http.StatusOK
+ case strings.Contains(r.URL.Path, "/exec/"):
+ ctnr, err := runtime.GetExecSessionContainer(name)
+ if err != nil {
+ utils.SessionNotFound(w, name, err)
+ return
+ }
+ if state, err := ctnr.State(); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain session container state"))
+ return
+ } else if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running", http.StatusConflict,
+ fmt.Errorf("container %q in wrong state %q", name, state.String()))
+ return
+ }
+ if err := ctnr.ExecResize(name, sz); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
+ return
+ }
+ // This is not a 204, even though we write nothing, for compatibility
+ // reasons.
+ status = http.StatusCreated
+ }
+ utils.WriteResponse(w, status, "")
+}
diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go
index ce83aa32f..dc94a7ebd 100644
--- a/pkg/api/handlers/compat/swagger.go
+++ b/pkg/api/handlers/compat/swagger.go
@@ -3,6 +3,7 @@ package compat
import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
+ "github.com/docker/docker/api/types"
)
// Create container
@@ -35,3 +36,30 @@ type swagChangesResponse struct {
Changes []archive.Change
}
}
+
+// Network inspect
+// swagger:response CompatNetworkInspect
+type swagCompatNetworkInspect struct {
+ // in:body
+ Body types.NetworkResource
+}
+
+// Network list
+// swagger:response CompatNetworkList
+type swagCompatNetworkList struct {
+ // in:body
+ Body []types.NetworkResource
+}
+
+// Network create
+// swagger:model NetworkCreateRequest
+type NetworkCreateRequest struct {
+ types.NetworkCreateRequest
+}
+
+// Network create
+// swagger:response CompatNetworkCreate
+type swagCompatNetworkCreateResponse struct {
+ // in:body
+ Body struct{ types.NetworkCreate }
+}
diff --git a/pkg/api/handlers/compat/types.go b/pkg/api/handlers/compat/types.go
index b8d06760f..6d47ede64 100644
--- a/pkg/api/handlers/compat/types.go
+++ b/pkg/api/handlers/compat/types.go
@@ -48,7 +48,7 @@ type StatsJSON struct {
Stats
Name string `json:"name,omitempty"`
- ID string `json:"id,omitempty"`
+ ID string `json:"Id,omitempty"`
// Networks request version >=1.21
Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go
index 35a95b562..bfc226bb8 100644
--- a/pkg/api/handlers/compat/version.go
+++ b/pkg/api/handlers/compat/version.go
@@ -8,8 +8,8 @@ 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/domain/entities"
docker "github.com/docker/docker/api/types"
"github.com/pkg/errors"
)
@@ -34,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
Name: "Podman Engine",
Version: versionInfo.Version,
Details: map[string]string{
- "APIVersion": handlers.DefaultApiVersion,
+ "APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(),
"Arch": goRuntime.GOARCH,
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
"Experimental": "true",
"GitCommit": versionInfo.GitCommit,
"GoVersion": versionInfo.GoVersion,
"KernelVersion": infoData.Host.Kernel,
- "MinAPIVersion": handlers.MinimalApiVersion,
+ "MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(),
"Os": goRuntime.GOOS,
},
}}
- utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{
- Platform: struct {
- Name string
- }{
- Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
- },
- APIVersion: components[0].Details["APIVersion"],
- Arch: components[0].Details["Arch"],
- BuildTime: components[0].Details["BuildTime"],
- Components: components,
- Experimental: true,
- GitCommit: components[0].Details["GitCommit"],
- GoVersion: components[0].Details["GoVersion"],
- KernelVersion: components[0].Details["KernelVersion"],
- MinAPIVersion: components[0].Details["MinAPIVersion"],
- Os: components[0].Details["Os"],
- Version: components[0].Version,
- }})
+ utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
+ Version: docker.Version{
+ Platform: struct {
+ Name string
+ }{
+ Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
+ },
+ APIVersion: components[0].Details["APIVersion"],
+ Arch: components[0].Details["Arch"],
+ BuildTime: components[0].Details["BuildTime"],
+ Components: components,
+ Experimental: true,
+ GitCommit: components[0].Details["GitCommit"],
+ GoVersion: components[0].Details["GoVersion"],
+ KernelVersion: components[0].Details["KernelVersion"],
+ MinAPIVersion: components[0].Details["MinAPIVersion"],
+ Os: components[0].Details["Os"],
+ Version: components[0].Version,
+ }})
}
diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go
deleted file mode 100644
index 2dd2c886b..000000000
--- a/pkg/api/handlers/handler.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package handlers
-
-const (
- DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
- MinimalApiVersion = "1.24"
-)
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index 3902bdc9b..50f6b1a38 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -66,6 +66,10 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
+ if len(pss) == 0 {
+ utils.WriteResponse(w, http.StatusOK, "[]")
+ return
+ }
utils.WriteResponse(w, http.StatusOK, pss)
}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index 40b6cacdb..71f440bce 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -5,10 +5,9 @@ import (
"encoding/json"
"net/http"
- "github.com/containers/libpod/pkg/domain/entities"
-
"github.com/containers/libpod/libpod"
"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/specgen/generate"
"github.com/pkg/errors"
diff --git a/pkg/api/handlers/libpod/copy.go b/pkg/api/handlers/libpod/copy.go
new file mode 100644
index 000000000..a3b404bce
--- /dev/null
+++ b/pkg/api/handlers/libpod/copy.go
@@ -0,0 +1,12 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func Archive(w http.ResponseWriter, r *http.Request) {
+ utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+}
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
new file mode 100644
index 000000000..23320d346
--- /dev/null
+++ b/pkg/api/handlers/libpod/generate.go
@@ -0,0 +1,38 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func GenerateKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Service bool `schema:"service"`
+ }{
+ // Defaults would go here.
+ }
+
+ 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
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.GenerateKubeOptions{Service: query.Service}
+ report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report.Reader)
+}
diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go
index 6eb2ab0e3..0ca3574b7 100644
--- a/pkg/api/handlers/libpod/healthcheck.go
+++ b/pkg/api/handlers/libpod/healthcheck.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
)
@@ -12,32 +13,27 @@ func RunHealthCheck(w http.ResponseWriter, r *http.Request) {
name := utils.GetName(r)
status, err := runtime.HealthCheck(name)
if err != nil {
- if status == libpod.HealthCheckContainerNotFound {
+ if status == define.HealthCheckContainerNotFound {
utils.ContainerNotFound(w, name, err)
return
}
- if status == libpod.HealthCheckNotDefined {
+ if status == define.HealthCheckNotDefined {
utils.Error(w, "no healthcheck defined", http.StatusConflict, err)
return
}
- if status == libpod.HealthCheckContainerStopped {
+ if status == define.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
+ hcStatus := define.HealthCheckUnhealthy
+ if status == define.HealthCheckSuccess {
+ hcStatus = define.HealthCheckHealthy
}
-
- hcLog, err := ctr.GetHealthCheckLog()
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ report := define.HealthCheckResults{
+ Status: hcStatus,
}
-
- utils.WriteResponse(w, http.StatusOK, hcLog)
+ utils.WriteResponse(w, http.StatusOK, report)
}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index f7be5ce9a..4b277d39c 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -21,12 +21,15 @@ 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/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/util"
utils2 "github.com/containers/libpod/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// Commit
@@ -55,13 +58,6 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
func ImageTree(w http.ResponseWriter, r *http.Request) {
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"`
@@ -73,14 +69,18 @@ func ImageTree(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
-
- tree, err := img.GenerateTree(query.WhatRequires)
+ ir := abi.ImageEngine{Libpod: runtime}
+ options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires}
+ report, err := ir.Tree(r.Context(), name, options)
if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchImage {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
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)
+ utils.WriteResponse(w, http.StatusOK, report)
}
func GetImage(w http.ResponseWriter, r *http.Request) {
@@ -341,7 +341,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
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"`
@@ -384,20 +383,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
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
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
}
+ defer auth.RemoveAuthfile(authfile)
// Setup the registry options
dockerRegistryOptions := image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
+ DockerRegistryCreds: authConf,
OSChoice: query.OverrideOS,
ArchitectureChoice: query.OverrideArch,
}
@@ -405,6 +400,13 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
+ sys := runtime.SystemContext()
+ if sys == nil {
+ sys = image.GetSystemContext("", authfile, false)
+ }
+ dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
+ sys.DockerAuthConfig = authConf
+
// Prepare the images we want to pull
imagesToPull := []string{}
res := []handlers.LibpodImagesPullReport{}
@@ -413,8 +415,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
if !query.AllTags {
imagesToPull = append(imagesToPull, imageName)
} else {
- systemContext := image.GetSystemContext("", "", false)
- tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef)
+ tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef)
if err != nil {
utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
return
@@ -424,12 +425,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
}
}
- authfile := ""
- if sys := runtime.SystemContext(); sys != nil {
- dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
- }
-
// Finally pull the images
for _, img := range imagesToPull {
newImage, err := runtime.ImageRuntime().New(
@@ -458,7 +453,6 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
- Credentials string `schema:"credentials"`
Destination string `schema:"destination"`
TLSVerify bool `schema:"tlsVerify"`
}{
@@ -494,26 +488,20 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
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
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
}
+ defer auth.RemoveAuthfile(authfile)
+ logrus.Errorf("AuthConf: %v", authConf)
- // TODO: the X-Registry-Auth header is not checked yet here nor in any other
- // endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
+ DockerRegistryCreds: authConf,
}
- authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
+ dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
if _, found := r.URL.Query()["tlsVerify"]; found {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
@@ -700,8 +688,8 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, reports)
}
-// ImagesRemove is the endpoint for image removal.
-func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+// ImagesBatchRemove is the endpoint for batch image removal.
+func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -722,7 +710,49 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
imageEngine := abi.ImageEngine{Libpod: runtime}
- rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
- report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
+
+ strErrs := errorhandling.ErrorsToStrings(rmErrors)
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs}
utils.WriteResponse(w, http.StatusOK, report)
}
+
+// ImagesRemove is the endpoint for removing one image.
+func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ Force: 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
+ }
+
+ opts := entities.ImageRemoveOptions{Force: query.Force}
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
+
+ // In contrast to batch-removal, where we're only setting the exit
+ // code, we need to have another closer look at the errors here and set
+ // the appropriate http status code.
+
+ switch rmReport.ExitCode {
+ case 0:
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
+ utils.WriteResponse(w, http.StatusOK, report)
+ case 1:
+ // 404 - no such image
+ utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
+ case 2:
+ // 409 - conflict error (in use by containers)
+ utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors))
+ default:
+ // 500 - internal error
+ utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
+ }
+}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index d87ed7eba..aef92368b 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -120,6 +120,10 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
}
func ManifestPush(w http.ResponseWriter, r *http.Request) {
+ // FIXME: parameters are missing (tlsVerify, format).
+ // Also, we should use the ABI function to avoid duplicate code.
+ // Also, support for XRegistryAuth headers are missing.
+
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -151,6 +155,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) {
}
sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
opts := manifests.PushOptions{
+ Store: runtime.GetStore(),
ImageListSelection: copy2.CopySpecificImages,
SystemContext: sc,
}
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index e8a92e93e..7de285e5e 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -1,39 +1,59 @@
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/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"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) {
+func CreateNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- config, err := runtime.GetConfig()
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ options := entities.NetworkCreateOptions{}
+ if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
+ utils.Error(w, "unable to marshall input", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ query := struct {
+ Name string `schema:"name"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ ic := abi.ContainerEngine{Libpod: runtime}
+ report, err := ic.NetworkCreate(r.Context(), query.Name, options)
if err != nil {
utils.InternalServerError(w, err)
return
}
- configDir := config.Network.NetworkConfigDir
- if len(configDir) < 1 {
- configDir = network.CNIConfigDir
- }
- networks, err := network.LoadCNIConfsFromDir(configDir)
+ utils.WriteResponse(w, http.StatusOK, report)
+
+}
+func ListNetworks(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ options := entities.NetworkListOptions{}
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkList(r.Context(), options)
if err != nil {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, networks)
+ utils.WriteResponse(w, http.StatusOK, reports)
}
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
- // 200 ok
- // 404 no such
- // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Force bool `schema:"force"`
@@ -46,22 +66,30 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
return
}
name := utils.GetName(r)
- if err := network.RemoveNetwork(name); err != nil {
+
+ options := entities.NetworkRmOptions{
+ Force: query.Force,
+ }
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if reports[0].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, "")
+ utils.WriteResponse(w, http.StatusOK, reports)
}
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
+ 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
}
@@ -71,7 +99,9 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
return
}
name := utils.GetName(r)
- n, err := network.InspectNetwork(name)
+ options := entities.NetworkInspectOptions{}
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkInspect(r.Context(), []string{name}, options)
if err != nil {
// If the network cannot be found, we return a 404.
if errors.Cause(err) == network.ErrNetworkNotFound {
@@ -81,5 +111,5 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, n)
+ utils.WriteResponse(w, http.StatusOK, reports)
}
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
new file mode 100644
index 000000000..1cb5cdb6c
--- /dev/null
+++ b/pkg/api/handlers/libpod/play.go
@@ -0,0 +1,82 @@
+package libpod
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PlayKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Network string `schema:"reference"`
+ TLSVerify bool `schema:"tlsVerify"`
+ }{
+ 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
+ }
+
+ // Fetch the K8s YAML file from the body, and copy it to a temp file.
+ tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ tmpfile.Close()
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
+ return
+ }
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+ var username, password string
+ if authConf != nil {
+ username = authConf.Username
+ password = authConf.Password
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.PlayKubeOptions{
+ Authfile: authfile,
+ Username: username,
+ Password: password,
+ Network: query.Network,
+ Quiet: true,
+ }
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index 46426eb6b..057fbfb41 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -91,6 +91,34 @@ type swagInfoResponse struct {
Body define.Info
}
+// Network rm
+// swagger:response NetworkRmReport
+type swagNetworkRmReport struct {
+ // in:body
+ Body entities.NetworkRmReport
+}
+
+// Network inspect
+// swagger:response NetworkInspectReport
+type swagNetworkInspectReport struct {
+ // in:body
+ Body []entities.NetworkInspectReport
+}
+
+// Network list
+// swagger:response NetworkListReport
+type swagNetworkListReport struct {
+ // in:body
+ Body []entities.NetworkListReport
+}
+
+// Network create
+// swagger:response NetworkCreateReport
+type swagNetworkCreateReport struct {
+ // in:body
+ Body entities.NetworkCreateReport
+}
+
func ServeSwagger(w http.ResponseWriter, r *http.Request) {
path := DefaultPodmanSwaggerSpec
if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go
index 98e33bf10..f575546c9 100644
--- a/pkg/api/handlers/libpod/system.go
+++ b/pkg/api/handlers/libpod/system.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers/compat"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -69,3 +70,15 @@ func SystemPrune(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, systemPruneReport)
}
+
+func DiskUsage(w http.ResponseWriter, r *http.Request) {
+ // Options are only used by the CLI
+ options := entities.SystemDfOptions{}
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ ic := abi.ContainerEngine{Libpod: runtime}
+ response, err := ic.SystemDf(r.Context(), options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, response)
+}
diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go
index 0aceaf5f6..5d125417b 100644
--- a/pkg/api/handlers/swagger/swagger.go
+++ b/pkg/api/handlers/swagger/swagger.go
@@ -56,6 +56,13 @@ type swagLibpodImagesRemoveResponse struct {
Body handlers.LibpodImagesRemoveReport
}
+// PlayKube response
+// swagger:response DocsLibpodPlayKubeResponse
+type swagLibpodPlayKubeResponse struct {
+ // in:body
+ Body entities.PlayKubeReport
+}
+
// Delete response
// swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 58a12ea6a..aa3d0fe91 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -41,7 +41,7 @@ type LibpodImagesPullReport struct {
type LibpodImagesRemoveReport struct {
entities.ImageRemoveReport
// Image removal requires is to return data and an error.
- Error string
+ Errors []string
}
type ContainersPruneReport struct {
@@ -71,10 +71,6 @@ type Container struct {
docker.ContainerCreateConfig
}
-type Version struct {
- docker.Version
-}
-
type DiskUsage struct {
docker.DiskUsage
}
@@ -124,7 +120,7 @@ type CreateContainerConfig struct {
// swagger:model IDResponse
type IDResponse struct {
// ID
- ID string `json:"id"`
+ ID string `json:"Id"`
}
type ContainerTopOKBody struct {
@@ -174,6 +170,11 @@ type ExecCreateResponse struct {
docker.IDResponse
}
+type ExecStartConfig struct {
+ Detach bool `json:"Detach"`
+ Tty bool `json:"Tty"`
+}
+
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
containers, err := l.Containers()
if err != nil {
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index 3253a9be3..c17720694 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -63,6 +63,14 @@ func PodNotFound(w http.ResponseWriter, name string, err error) {
Error(w, msg, http.StatusNotFound, err)
}
+func SessionNotFound(w http.ResponseWriter, name string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchExecSession {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such exec session: %s", name)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
msg := fmt.Sprintf("Container %s is not running", containerID)
Error(w, msg, http.StatusConflict, err)
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index b5bd488fb..2f4a54b98 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -9,11 +9,55 @@ import (
"os"
"strings"
+ "github.com/blang/semver"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+type (
+ // VersionTree determines which API endpoint tree for version
+ VersionTree int
+ // VersionLevel determines which API level, current or something from the past
+ VersionLevel int
+)
+
+const (
+ // LibpodTree supports Libpod endpoints
+ LibpodTree = VersionTree(iota)
+ // CompatTree supports Libpod endpoints
+ CompatTree
+
+ // CurrentApiVersion announces what is the current API level
+ CurrentApiVersion = VersionLevel(iota)
+ // MinimalApiVersion announces what is the oldest API level supported
+ MinimalApiVersion
+)
+
+var (
+ // See https://docs.docker.com/engine/api/v1.40/
+ // libpod compat handlers are expected to honor docker API versions
+
+ // ApiVersion provides the current and minimal API versions for compat and libpod endpoint trees
+ // Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow
+ // clients to shop for the Version they wish to support
+ ApiVersion = map[VersionTree]map[VersionLevel]semver.Version{
+ LibpodTree: {
+ CurrentApiVersion: semver.MustParse("1.0.0"),
+ MinimalApiVersion: semver.MustParse("1.0.0"),
+ },
+ CompatTree: {
+ CurrentApiVersion: semver.MustParse("1.40.0"),
+ MinimalApiVersion: semver.MustParse("1.24.0"),
+ },
+ }
+
+ // ErrVersionNotGiven returned when version not given by client
+ ErrVersionNotGiven = errors.New("version not given in URL path")
+ // ErrVersionNotSupported returned when given version is too old
+ ErrVersionNotSupported = errors.New("given version is not supported")
+)
+
// IsLibpodRequest returns true if the request related to a libpod endpoint
// (e.g., /v2/libpod/...).
func IsLibpodRequest(r *http.Request) bool {
@@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool {
return len(split) >= 3 && split[2] == "libpod"
}
+// SupportedVersion validates that the version provided by client is included in the given condition
+// https://github.com/blang/semver#ranges provides the details for writing conditions
+// If a version is not given in URL path, ErrVersionNotGiven is returned
+func SupportedVersion(r *http.Request, condition string) (semver.Version, error) {
+ version := semver.Version{}
+ val, ok := mux.Vars(r)["version"]
+ if !ok {
+ return version, ErrVersionNotGiven
+ }
+ safeVal, err := url.PathUnescape(val)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to unescape given API version: %q", val)
+ }
+ version, err = semver.ParseTolerant(safeVal)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val)
+ }
+
+ inRange, err := semver.ParseRange(condition)
+ if err != nil {
+ return version, err
+ }
+
+ if inRange(version) {
+ return version, nil
+ }
+ return version, ErrVersionNotSupported
+}
+
+// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server
+// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL
+func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) {
+ tree := CompatTree
+ if IsLibpodRequest(r) {
+ tree = LibpodTree
+ }
+
+ return SupportedVersion(r,
+ fmt.Sprintf(">=%s <=%s", ApiVersion[tree][MinimalApiVersion].String(),
+ ApiVersion[tree][CurrentApiVersion].String()))
+}
+
// 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
diff --git a/pkg/api/handlers/utils/handler_test.go b/pkg/api/handlers/utils/handler_test.go
new file mode 100644
index 000000000..6009432b5
--- /dev/null
+++ b/pkg/api/handlers/utils/handler_test.go
@@ -0,0 +1,139 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gorilla/mux"
+)
+
+func TestSupportedVersion(t *testing.T) {
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", ApiVersion[LibpodTree][CurrentApiVersion]),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": ApiVersion[LibpodTree][CurrentApiVersion].String()})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := `OK`
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestUnsupportedVersion(t *testing.T) {
+ version := "999.999.999"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusBadRequest)
+ }
+
+ // Check the response body is what we expect.
+ expected := ErrVersionNotSupported.Error()
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestEqualVersion(t *testing.T) {
+ version := "1.30.0"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersion(r, "=="+version)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := http.StatusText(http.StatusOK)
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
index 1c67de9db..7fb31a177 100644
--- a/pkg/api/handlers/utils/images.go
+++ b/pkg/api/handlers/utils/images.go
@@ -62,7 +62,6 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
}{
// 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
@@ -71,6 +70,10 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
if _, found := r.URL.Query()["digests"]; found && query.Digests {
UnSupportedParameter("digests")
}
+ var (
+ images []*image.Image
+ err error
+ )
if len(query.Filters) > 0 {
for k, v := range query.Filters {
@@ -78,11 +81,33 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
- return runtime.ImageRuntime().GetImagesWithFilters(filters)
+ images, err = runtime.ImageRuntime().GetImagesWithFilters(filters)
+ if err != nil {
+ return images, err
+ }
} else {
- return runtime.ImageRuntime().GetImages()
+ images, err = runtime.ImageRuntime().GetImages()
+ if err != nil {
+ return images, err
+ }
}
-
+ if query.All {
+ return images, nil
+ }
+ var returnImages []*image.Image
+ for _, img := range images {
+ if len(img.Names()) == 0 {
+ parent, err := img.IsParent(r.Context())
+ if err != nil {
+ return nil, err
+ }
+ if parent {
+ continue
+ }
+ }
+ returnImages = append(returnImages, img)
+ }
+ return returnImages, nil
}
func GetImage(r *http.Request, name string) (*image.Image, error) {