diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index c67093950d..ece336bef4 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" + dockerimage "github.com/docker/docker/image" specs "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -23,7 +24,7 @@ type imageBackend interface { ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) ImageHistory(imageName string) ([]*image.HistoryResponseItem, error) Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) - LookupImage(name string) (*types.ImageInspect, error) + GetImage(refOrID string, platform *specs.Platform) (retImg *dockerimage.Image, retErr error) TagImage(imageName, repository, tag string) (string, error) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) } diff --git a/api/server/router/image/image.go b/api/server/router/image/image.go index 42f89153b5..e7ab7f0b64 100644 --- a/api/server/router/image/image.go +++ b/api/server/router/image/image.go @@ -2,17 +2,28 @@ package image // import "github.com/docker/docker/api/server/router/image" import ( "github.com/docker/docker/api/server/router" + "github.com/docker/docker/image" + "github.com/docker/docker/layer" + "github.com/docker/docker/reference" ) // imageRouter is a router to talk with the image controller type imageRouter struct { - backend Backend - routes []router.Route + backend Backend + referenceBackend reference.Store + imageStore image.Store + layerStore layer.Store + routes []router.Route } // NewRouter initializes a new image router -func NewRouter(backend Backend) router.Router { - r := &imageRouter{backend: backend} +func NewRouter(backend Backend, referenceBackend reference.Store, imageStore image.Store, layerStore layer.Store) router.Router { + r := &imageRouter{ + backend: backend, + referenceBackend: referenceBackend, + imageStore: imageStore, + layerStore: layerStore, + } r.initRoutes() return r } diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index 05a7d6bfbb..b186e61ade 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -7,13 +7,17 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/containerd/containerd/platforms" + "github.com/docker/distribution/reference" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" + "github.com/docker/docker/image" + "github.com/docker/docker/layer" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/streamformatter" specs "github.com/opencontainers/image-spec/specs-go/v1" @@ -200,7 +204,12 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r } func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - imageInspect, err := s.backend.LookupImage(vars["name"]) + image, err := s.backend.GetImage(vars["name"], nil) + if err != nil { + return err + } + + imageInspect, err := s.toImageInspect(image) if err != nil { return err } @@ -208,6 +217,85 @@ func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter return httputils.WriteJSON(w, http.StatusOK, imageInspect) } +func (s *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, error) { + refs := s.referenceBackend.References(img.ID().Digest()) + repoTags := []string{} + repoDigests := []string{} + for _, ref := range refs { + switch ref.(type) { + case reference.NamedTagged: + repoTags = append(repoTags, reference.FamiliarString(ref)) + case reference.Canonical: + repoDigests = append(repoDigests, reference.FamiliarString(ref)) + } + } + + var size int64 + var layerMetadata map[string]string + layerID := img.RootFS.ChainID() + if layerID != "" { + l, err := s.layerStore.Get(layerID) + if err != nil { + return nil, err + } + defer layer.ReleaseAndLog(s.layerStore, l) + size = l.Size() + layerMetadata, err = l.Metadata() + if err != nil { + return nil, err + } + } + + comment := img.Comment + if len(comment) == 0 && len(img.History) > 0 { + comment = img.History[len(img.History)-1].Comment + } + + lastUpdated, err := s.imageStore.GetLastUpdated(img.ID()) + if err != nil { + return nil, err + } + + return &types.ImageInspect{ + ID: img.ID().String(), + RepoTags: repoTags, + RepoDigests: repoDigests, + Parent: img.Parent.String(), + Comment: comment, + Created: img.Created.Format(time.RFC3339Nano), + Container: img.Container, + ContainerConfig: &img.ContainerConfig, + DockerVersion: img.DockerVersion, + Author: img.Author, + Config: img.Config, + Architecture: img.Architecture, + Variant: img.Variant, + Os: img.OperatingSystem(), + OsVersion: img.OSVersion, + Size: size, + VirtualSize: size, // TODO: field unused, deprecate + GraphDriver: types.GraphDriverData{ + Name: s.layerStore.DriverName(), + Data: layerMetadata, + }, + RootFS: rootFSToAPIType(img.RootFS), + Metadata: types.ImageMetadata{ + LastTagTime: lastUpdated, + }, + }, nil +} + +func rootFSToAPIType(rootfs *image.RootFS) types.RootFS { + var layers []string + for _, l := range rootfs.DiffIDs { + layers = append(layers, l.String()) + } + return types.RootFS{ + Type: rootfs.Type, + Layers: layers, + } +} + func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index 2fdcb5b8ab..5c8eb1df73 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -529,7 +529,12 @@ func initRouter(opts routerOptions) { // we need to add the checkpoint router before the container router or the DELETE gets masked checkpointrouter.NewRouter(opts.daemon, decoder), container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified), - image.NewRouter(opts.daemon.ImageService()), + image.NewRouter( + opts.daemon.ImageService(), + opts.daemon.ReferenceStore, + opts.daemon.ImageService().DistributionServices().ImageStore, + opts.daemon.ImageService().DistributionServices().LayerStore, + ), systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildkit, opts.features), volume.NewRouter(opts.daemon.VolumesService(), opts.cluster), build.NewRouter(opts.buildBackend, opts.daemon, opts.features), diff --git a/daemon/cluster/executor/backend.go b/daemon/cluster/executor/backend.go index 89b883ea2f..cbd9fe33e8 100644 --- a/daemon/cluster/executor/backend.go +++ b/daemon/cluster/executor/backend.go @@ -18,6 +18,7 @@ import ( containerpkg "github.com/docker/docker/container" clustertypes "github.com/docker/docker/daemon/cluster/provider" networkSettings "github.com/docker/docker/daemon/network" + "github.com/docker/docker/image" "github.com/docker/docker/libnetwork" "github.com/docker/docker/libnetwork/cluster" networktypes "github.com/docker/docker/libnetwork/types" @@ -74,5 +75,5 @@ type VolumeBackend interface { type ImageBackend interface { PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, error) - LookupImage(name string) (*types.ImageInspect, error) + GetImage(refOrID string, platform *specs.Platform) (retImg *image.Image, retErr error) } diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index 4ed45526d2..925aa058ab 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -74,7 +74,7 @@ func (c *containerAdapter) pullImage(ctx context.Context) error { named, err := reference.ParseNormalizedNamed(spec.Image) if err == nil { if _, ok := named.(reference.Canonical); ok { - _, err := c.imageBackend.LookupImage(spec.Image) + _, err := c.imageBackend.GetImage(spec.Image, nil) if err == nil { return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index bb09038b63..9ae5bfd297 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -105,6 +105,7 @@ type Daemon struct { cluster Cluster genericResources []swarm.GenericResource metricsPluginListener net.Listener + ReferenceStore refstore.Store machineMemory uint64 @@ -999,6 +1000,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S if err != nil { return nil, fmt.Errorf("Couldn't create reference store repository: %s", err) } + d.ReferenceStore = rs distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution")) if err != nil { diff --git a/daemon/images/image_inspect.go b/daemon/images/image_inspect.go deleted file mode 100644 index c3cb113c97..0000000000 --- a/daemon/images/image_inspect.go +++ /dev/null @@ -1,97 +0,0 @@ -package images // import "github.com/docker/docker/daemon/images" - -import ( - "time" - - "github.com/docker/distribution/reference" - "github.com/docker/docker/api/types" - "github.com/docker/docker/image" - "github.com/docker/docker/layer" - "github.com/pkg/errors" -) - -// LookupImage looks up an image by name and returns it as an ImageInspect -// structure. -func (i *ImageService) LookupImage(name string) (*types.ImageInspect, error) { - img, err := i.GetImage(name, nil) - if err != nil { - return nil, errors.Wrapf(err, "no such image: %s", name) - } - - refs := i.referenceStore.References(img.ID().Digest()) - repoTags := []string{} - repoDigests := []string{} - for _, ref := range refs { - switch ref.(type) { - case reference.NamedTagged: - repoTags = append(repoTags, reference.FamiliarString(ref)) - case reference.Canonical: - repoDigests = append(repoDigests, reference.FamiliarString(ref)) - } - } - - var size int64 - var layerMetadata map[string]string - layerID := img.RootFS.ChainID() - if layerID != "" { - l, err := i.layerStore.Get(layerID) - if err != nil { - return nil, err - } - defer layer.ReleaseAndLog(i.layerStore, l) - size = l.Size() - layerMetadata, err = l.Metadata() - if err != nil { - return nil, err - } - } - - comment := img.Comment - if len(comment) == 0 && len(img.History) > 0 { - comment = img.History[len(img.History)-1].Comment - } - - lastUpdated, err := i.imageStore.GetLastUpdated(img.ID()) - if err != nil { - return nil, err - } - - return &types.ImageInspect{ - ID: img.ID().String(), - RepoTags: repoTags, - RepoDigests: repoDigests, - Parent: img.Parent.String(), - Comment: comment, - Created: img.Created.Format(time.RFC3339Nano), - Container: img.Container, - ContainerConfig: &img.ContainerConfig, - DockerVersion: img.DockerVersion, - Author: img.Author, - Config: img.Config, - Architecture: img.Architecture, - Variant: img.Variant, - Os: img.OperatingSystem(), - OsVersion: img.OSVersion, - Size: size, - VirtualSize: size, // TODO: field unused, deprecate - GraphDriver: types.GraphDriverData{ - Name: i.layerStore.DriverName(), - Data: layerMetadata, - }, - RootFS: rootFSToAPIType(img.RootFS), - Metadata: types.ImageMetadata{ - LastTagTime: lastUpdated, - }, - }, nil -} - -func rootFSToAPIType(rootfs *image.RootFS) types.RootFS { - var layers []string - for _, l := range rootfs.DiffIDs { - layers = append(layers, l.String()) - } - return types.RootFS{ - Type: rootfs.Type, - Layers: layers, - } -}