diff --git a/go.mod b/go.mod index af1be72aab0..750cd8742b1 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/godbus/dbus v0.0.0-20181101234600-2ff6f7ffd60f github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf + github.com/gorilla/mux v1.7.3 github.com/hashicorp/go-multierror v1.0.0 github.com/hpcloud/tail v1.0.0 github.com/imdario/mergo v0.3.7 // indirect diff --git a/pkg/serviceapi/handler_containers.go b/pkg/serviceapi/handler_containers.go index d2beab6b4a0..dcd0f5c50f0 100644 --- a/pkg/serviceapi/handler_containers.go +++ b/pkg/serviceapi/handler_containers.go @@ -4,8 +4,14 @@ import ( "net/http" "github.com/containers/libpod/libpod" + "github.com/gorilla/mux" ) +func registerContainersHandlers(r *mux.Router) error { + r.Handle(versionedPath("/containers/"), serviceHandler(containers)) + return nil +} + func containers(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { http.NotFound(w, r) } diff --git a/pkg/serviceapi/handler_images.go b/pkg/serviceapi/handler_images.go index ecde5be6444..6bd9681b0f6 100644 --- a/pkg/serviceapi/handler_images.go +++ b/pkg/serviceapi/handler_images.go @@ -1,15 +1,23 @@ package serviceapi import ( + "context" "encoding/json" "fmt" "io" "net/http" "github.com/containers/libpod/libpod" + "github.com/gorilla/mux" ) -func images(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { +func registerImagesHandlers(r *mux.Router) error { + r.Handle(versionedPath("/images/json"), serviceHandler(getImages)) + r.Handle(versionedPath("/images/{name:..*}/json"), serviceHandler(inspectImages)) + return nil +} +func inspectImages(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { + // /v1.24/images/(name) contentType := r.Header.Get("Content-Type") if contentType != "" && contentType != "application/json" { http.Error(w, @@ -18,9 +26,40 @@ func images(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { return } + name := mux.Vars(r)["name"] + image, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + http.Error(w, fmt.Sprintf("Image '%s' not found", name), http.StatusNotFound) + return + } + + info, err := image.Inspect(context.Background()) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to inspect Image '%s'", name), http.StatusInternalServerError) + return + } + + inspect, err := ImageDataToImageInspect(info) + buffer, err := json.Marshal(inspect) + if err != nil { + http.Error(w, + fmt.Sprintf("Failed to convert API ImageInspect '%s' to json: %s", inspect.ID, err.Error()), + http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + io.WriteString(w, string(buffer)) +} + +func getImages(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { + // /v1.24/images/json + images, err := runtime.ImageRuntime().GetImages() if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, + fmt.Sprintf("Failed to obtain the list of images from storage: %s", err.Error()), + http.StatusInternalServerError) return } @@ -28,14 +67,19 @@ func images(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { for _, img := range images { i, err := ImageToImageSummary(img) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, + fmt.Sprintf("Failed to convert storage image '%s' to API image: %s", img.ID(), err.Error()), + http.StatusInternalServerError) + return } summaries = append(summaries, i) } buffer, err := json.Marshal(summaries) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, + fmt.Sprintf("Failed to convert API images to json: %s", err.Error()), + http.StatusInternalServerError) return } diff --git a/pkg/serviceapi/handler_info.go b/pkg/serviceapi/handler_info.go new file mode 100644 index 00000000000..4f0fbde52d2 --- /dev/null +++ b/pkg/serviceapi/handler_info.go @@ -0,0 +1,44 @@ +package serviceapi + + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/gorilla/mux" +) + +func registerInfoHandlers(r *mux.Router) error { + r.Handle(versionedPath("/info"), serviceHandler(info)) + return nil +} + +func info(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { + infoData, err := runtime.Info() + if err != nil { + http.Error(w, + fmt.Sprintf("Failed to obtain the system information: %s", err.Error()), + http.StatusInternalServerError) + return + } + info, err := InfoDataToInfo(infoData) + if err != nil { + http.Error(w, + fmt.Sprintf("Failed to convert system information to API information: %s", err.Error()), + http.StatusInternalServerError) + return + } + + buffer, err := json.Marshal(info) + if err != nil { + http.Error(w, + fmt.Sprintf("Failed to convert API images to json: %s", err.Error()), + http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + io.WriteString(w, string(buffer)) +} diff --git a/pkg/serviceapi/handler_pods.go b/pkg/serviceapi/handler_pods.go new file mode 100644 index 00000000000..dd80c129b31 --- /dev/null +++ b/pkg/serviceapi/handler_pods.go @@ -0,0 +1,17 @@ +package serviceapi + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/gorilla/mux" +) + +func registerPodsHandlers(r *mux.Router) error { + r.Handle(versionedPath("/pods/"), serviceHandler(pods)) + return nil +} + +func pods(w http.ResponseWriter, r *http.Request, runtime *libpod.Runtime) { + http.NotFound(w, r) +} diff --git a/pkg/serviceapi/server.go b/pkg/serviceapi/server.go index bb915cef813..3c9fa4d9c23 100644 --- a/pkg/serviceapi/server.go +++ b/pkg/serviceapi/server.go @@ -10,14 +10,18 @@ import ( "time" "github.com/containers/libpod/libpod" + "github.com/gorilla/mux" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/coreos/go-systemd/activation" ) +const ApiVersion = "v1.24" + type HttpServer struct { http.Server + router *mux.Router done chan struct{} listener net.Listener } @@ -39,7 +43,13 @@ func NewServer(runtime *libpod.Runtime) (*HttpServer, error) { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - server := HttpServer{http.Server{}, done, listeners[0]} + router := mux.NewRouter() + registerImagesHandlers(router) + registerContainersHandlers(router) + registerPodsHandlers(router) + registerInfoHandlers(router) + + server := HttpServer{http.Server{}, router, done, listeners[0]} go func() { <-quit log.Debugf("HttpServer is shutting down") @@ -52,14 +62,11 @@ func NewServer(runtime *libpod.Runtime) (*HttpServer, error) { close(done) }() - // TODO: build this into a map... - http.Handle("/v1.24/images/json", serviceHandler(images)) - http.Handle("/v1.24/containers/json", serviceHandler(containers)) return &server, nil } func (s *HttpServer) Serve() error { - err := http.Serve(s.listener, nil) + err := http.Serve(s.listener, s.router) if err != nil { return errors.Wrap(err, "Failed to start HttpServer") } @@ -75,3 +82,7 @@ func (s *HttpServer) Shutdown(ctx context.Context) error { func (s *HttpServer) Close() error { return s.Server.Close() } + +func versionedPath(p string) string { + return "/" + ApiVersion + p +} diff --git a/pkg/serviceapi/types.go b/pkg/serviceapi/types.go index eab16398f56..afe86150583 100644 --- a/pkg/serviceapi/types.go +++ b/pkg/serviceapi/types.go @@ -2,17 +2,38 @@ package serviceapi import ( "context" + goRuntime "runtime" + "time" - podman "github.com/containers/libpod/libpod/image" + podmanDefine "github.com/containers/libpod/libpod/define" + podmanImage "github.com/containers/libpod/libpod/image" + podmanInspect "github.com/containers/libpod/pkg/inspect" + "github.com/containers/storage/pkg/system" docker "github.com/docker/docker/api/types" + dockerContainer "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" ) +type ImageInspect struct { + docker.ImageInspect +} + +type ContainerConfig struct { + dockerContainer.Config +} + type ImageSummary struct { docker.ImageSummary } -func ImageToImageSummary(p *podman.Image) (*ImageSummary, error) { +type Info struct { + docker.Info + BuildahVersion string + Rootless bool +} + +func ImageToImageSummary(p *podmanImage.Image) (*ImageSummary, error) { containers, err := p.Containers() if err != nil { return nil, errors.Wrapf(err, "Failed to obtain Containers for image %s", p.ID()) @@ -29,6 +50,7 @@ func ImageToImageSummary(p *podman.Image) (*ImageSummary, error) { return nil, errors.Wrapf(err, "Failed to obtain RepoTags for image %s", p.ID()) } + // FIXME: GetParent() panics // parent, err := p.GetParent(context.TODO()) // if err != nil { // return nil, errors.Wrapf(err, "Failed to obtain ParentID for image %s", p.ID()) @@ -56,3 +78,103 @@ func ImageToImageSummary(p *podman.Image) (*ImageSummary, error) { VirtualSize: int64(*size), }}, nil } + +func ImageDataToImageInspect(p *podmanInspect.ImageData) (*ImageInspect, error) { + return &ImageInspect{docker.ImageInspect{ + Architecture: p.Architecture, + Author: p.Author, + Comment: p.Comment, + Config: &dockerContainer.Config{}, + Container: "", + ContainerConfig: nil, + Created: p.Created.Format(time.RFC3339Nano), + DockerVersion: "", + GraphDriver: docker.GraphDriverData{}, + ID: p.ID, + Metadata: docker.ImageMetadata{}, + Os: p.Os, + OsVersion: p.Version, + Parent: p.Parent, + RepoDigests: p.RepoDigests, + RepoTags: p.RepoTags, + RootFS: docker.RootFS{}, + Size: p.Size, + Variant: "", + VirtualSize: p.VirtualSize, + }}, nil +} + +func InfoDataToInfo(p []podmanDefine.InfoData) (*Info, error) { + memInfo, err := system.ReadMemInfo() + if err != nil { + return nil, errors.Wrap(err, "Failed to obtain system memory info") + } + + return &Info{Info: docker.Info{ + Architecture: goRuntime.GOARCH, + BridgeNfIP6tables: false, + BridgeNfIptables: false, + CPUCfsPeriod: false, + CPUCfsQuota: false, + CPUSet: false, + CPUShares: false, + CgroupDriver: "", + ClusterAdvertise: "", + ClusterStore: "", + ContainerdCommit: docker.Commit{}, + Containers: 0, + ContainersPaused: 0, + ContainersRunning: 0, + ContainersStopped: 0, + Debug: false, + DefaultRuntime: "", + DockerRootDir: "", + Driver: "", + DriverStatus: nil, + ExperimentalBuild: false, + GenericResources: nil, + HTTPProxy: "", + HTTPSProxy: "", + ID: "podman", + IPv4Forwarding: false, + Images: 0, + IndexServerAddress: "", + InitBinary: "", + InitCommit: docker.Commit{}, + Isolation: "", + KernelMemory: false, + KernelMemoryTCP: false, + KernelVersion: "", + Labels: nil, + LiveRestoreEnabled: false, + LoggingDriver: "", + MemTotal: memInfo.MemTotal, + MemoryLimit: false, + NCPU: goRuntime.NumCPU(), + NEventsListener: 0, + NFd: 0, + NGoroutines: 0, + Name: "", + NoProxy: "", + OSType: "", + OSVersion: "", + OomKillDisable: false, + OperatingSystem: goRuntime.GOOS, + PidsLimit: false, + Plugins: docker.PluginsInfo{}, + ProductLicense: "", + RegistryConfig: nil, + RuncCommit: docker.Commit{}, + Runtimes: nil, + SecurityOptions: nil, + ServerVersion: "", + SwapLimit: false, + Swarm: swarm.Info{}, + SystemStatus: nil, + SystemTime: time.Now().Format(time.RFC3339Nano), + Warnings: nil, + }, + Rootless: false, + BuildahVersion: "", + }, nil +}