diff options
Diffstat (limited to 'pkg/api/handlers/compat')
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 27 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_archive.go | 12 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 51 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_start.go | 9 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_stats.go | 6 | ||||
-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.go | 12 | ||||
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 72 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images.go | 30 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 1 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_push.go | 15 | ||||
-rw-r--r-- | pkg/api/handlers/compat/networks.go | 301 | ||||
-rw-r--r-- | pkg/api/handlers/compat/ping.go | 8 | ||||
-rw-r--r-- | pkg/api/handlers/compat/resize.go | 86 | ||||
-rw-r--r-- | pkg/api/handlers/compat/swagger.go | 28 | ||||
-rw-r--r-- | pkg/api/handlers/compat/types.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/version.go | 43 |
17 files changed, 598 insertions, 105 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 + ®istryOpts, + 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, + }}) } |