diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 2997eb2169..04e59d58ff 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -1,12 +1,14 @@ package compat import ( + "context" "encoding/json" "errors" "fmt" "net/http" "os" "strings" + "time" "github.com/containers/buildah" "github.com/containers/common/libimage" @@ -24,7 +26,10 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage" "github.com/docker/distribution/registry/api/errcode" + docker "github.com/docker/docker/api/types" + dockerContainer "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/go-connections/nat" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" ) @@ -418,7 +423,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s: %s", name, errMsg)) return } - inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) + inspect, err := imageDataToImageInspect(r.Context(), newImage) if err != nil { utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to convert ImageData to ImageInspect '%s': %w", name, err)) return @@ -426,6 +431,99 @@ func GetImage(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, inspect) } +func imageDataToImageInspect(ctx context.Context, l *libimage.Image) (*handlers.ImageInspect, error) { + options := &libimage.InspectOptions{WithParent: true, WithSize: true} + info, err := l.Inspect(ctx, options) + if err != nil { + return nil, err + } + ports, err := portsToPortSet(info.Config.ExposedPorts) + if err != nil { + return nil, err + } + + // TODO: many fields in Config still need wiring + config := dockerContainer.Config{ + User: info.User, + ExposedPorts: ports, + Env: info.Config.Env, + Cmd: info.Config.Cmd, + Volumes: info.Config.Volumes, + WorkingDir: info.Config.WorkingDir, + Entrypoint: info.Config.Entrypoint, + Labels: info.Labels, + StopSignal: info.Config.StopSignal, + } + + rootfs := docker.RootFS{} + if info.RootFS != nil { + rootfs.Type = info.RootFS.Type + rootfs.Layers = make([]string, 0, len(info.RootFS.Layers)) + for _, layer := range info.RootFS.Layers { + rootfs.Layers = append(rootfs.Layers, string(layer)) + } + } + + graphDriver := docker.GraphDriverData{ + Name: info.GraphDriver.Name, + Data: info.GraphDriver.Data, + } + // Add in basic ContainerConfig to satisfy docker-compose + cc := new(dockerContainer.Config) + cc.Hostname = info.ID[0:11] // short ID is the hostname + cc.Volumes = info.Config.Volumes + + dockerImageInspect := docker.ImageInspect{ + Architecture: info.Architecture, + Author: info.Author, + Comment: info.Comment, + Config: &config, + ContainerConfig: cc, + Created: l.Created().Format(time.RFC3339Nano), + DockerVersion: info.Version, + GraphDriver: graphDriver, + ID: "sha256:" + l.ID(), + Metadata: docker.ImageMetadata{}, + Os: info.Os, + OsVersion: info.Version, + Parent: info.Parent, + RepoDigests: info.RepoDigests, + RepoTags: info.RepoTags, + RootFS: rootfs, + Size: info.Size, + Variant: "", + VirtualSize: info.VirtualSize, + } + return &handlers.ImageInspect{ImageInspect: dockerImageInspect}, nil +} + +// portsToPortSet converts libpod's exposed ports to docker's structs +func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) { + ports := make(nat.PortSet) + for k := range input { + proto, port := nat.SplitProtoPort(k) + switch proto { + // See the OCI image spec for details: + // https://github.com/opencontainers/image-spec/blob/e562b04403929d582d449ae5386ff79dd7961a11/config.md#properties + case "tcp", "": + p, err := nat.NewPort("tcp", port) + if err != nil { + return nil, fmt.Errorf("unable to create tcp port from %s: %w", k, err) + } + ports[p] = struct{}{} + case "udp": + p, err := nat.NewPort("udp", port) + if err != nil { + return nil, fmt.Errorf("unable to create tcp port from %s: %w", k, err) + } + ports[p] = struct{}{} + default: + return nil, fmt.Errorf("invalid port proto %q in %q", proto, k) + } + } + return ports, nil +} + func GetImages(w http.ResponseWriter, r *http.Request) { decoder := utils.GetDecoder(r) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index bb416d9f41..b8059bafb1 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -1,16 +1,10 @@ package handlers import ( - "context" - "fmt" - "time" - - "github.com/containers/common/libimage" "github.com/containers/podman/v4/pkg/domain/entities" docker "github.com/docker/docker/api/types" dockerContainer "github.com/docker/docker/api/types/container" dockerNetwork "github.com/docker/docker/api/types/network" - "github.com/docker/go-connections/nat" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -167,96 +161,3 @@ type ExecStartConfig struct { Height uint16 `json:"h"` Width uint16 `json:"w"` } - -func ImageDataToImageInspect(ctx context.Context, l *libimage.Image) (*ImageInspect, error) { - options := &libimage.InspectOptions{WithParent: true, WithSize: true} - info, err := l.Inspect(ctx, options) - if err != nil { - return nil, err - } - ports, err := portsToPortSet(info.Config.ExposedPorts) - if err != nil { - return nil, err - } - - // TODO: many fields in Config still need wiring - config := dockerContainer.Config{ - User: info.User, - ExposedPorts: ports, - Env: info.Config.Env, - Cmd: info.Config.Cmd, - Volumes: info.Config.Volumes, - WorkingDir: info.Config.WorkingDir, - Entrypoint: info.Config.Entrypoint, - Labels: info.Labels, - StopSignal: info.Config.StopSignal, - } - - rootfs := docker.RootFS{} - if info.RootFS != nil { - rootfs.Type = info.RootFS.Type - rootfs.Layers = make([]string, 0, len(info.RootFS.Layers)) - for _, layer := range info.RootFS.Layers { - rootfs.Layers = append(rootfs.Layers, string(layer)) - } - } - - graphDriver := docker.GraphDriverData{ - Name: info.GraphDriver.Name, - Data: info.GraphDriver.Data, - } - // Add in basic ContainerConfig to satisfy docker-compose - cc := new(dockerContainer.Config) - cc.Hostname = info.ID[0:11] // short ID is the hostname - cc.Volumes = info.Config.Volumes - - dockerImageInspect := docker.ImageInspect{ - Architecture: info.Architecture, - Author: info.Author, - Comment: info.Comment, - Config: &config, - ContainerConfig: cc, - Created: l.Created().Format(time.RFC3339Nano), - DockerVersion: info.Version, - GraphDriver: graphDriver, - ID: "sha256:" + l.ID(), - Metadata: docker.ImageMetadata{}, - Os: info.Os, - OsVersion: info.Version, - Parent: info.Parent, - RepoDigests: info.RepoDigests, - RepoTags: info.RepoTags, - RootFS: rootfs, - Size: info.Size, - Variant: "", - VirtualSize: info.VirtualSize, - } - return &ImageInspect{dockerImageInspect}, nil -} - -// portsToPortSet converts libpod's exposed ports to docker's structs -func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) { - ports := make(nat.PortSet) - for k := range input { - proto, port := nat.SplitProtoPort(k) - switch proto { - // See the OCI image spec for details: - // https://github.com/opencontainers/image-spec/blob/e562b04403929d582d449ae5386ff79dd7961a11/config.md#properties - case "tcp", "": - p, err := nat.NewPort("tcp", port) - if err != nil { - return nil, fmt.Errorf("unable to create tcp port from %s: %w", k, err) - } - ports[p] = struct{}{} - case "udp": - p, err := nat.NewPort("udp", port) - if err != nil { - return nil, fmt.Errorf("unable to create tcp port from %s: %w", k, err) - } - ports[p] = struct{}{} - default: - return nil, fmt.Errorf("invalid port proto %q in %q", proto, k) - } - } - return ports, nil -}