images: Extract ImageInspect from GetImage
Remove a special `Details` parameter from the `GetImage` options and
extract its behavior to a `ImageInspect` method as it was only used by
the `/images/{name}/json` endpoint (`docker image inspect`).
This makes it easier for the containerd image service to output an image
inspect output without having to use the same data structures as the
graphdrivers.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
8 files changed
+278
-208
No files matched your search
@@ -26,6 +26,7 @@ type imageBackend interface {
|
||||
ImageHistory(ctx context.Context, imageName string) ([]*image.HistoryResponseItem, error)
|
||||
Images(ctx context.Context, opts image.ListOptions) ([]*image.Summary, error)
|
||||
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*dockerimage.Image, error)
|
||||
ImageInspect(ctx context.Context, refOrID string, options backend.ImageInspectOpts) (*image.InspectResponse, error)
|
||||
TagImage(ctx context.Context, id dockerimage.ID, newRef reference.Named) error
|
||||
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*image.PruneReport, error)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/storage"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/builder/remotecontext"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
@@ -303,14 +302,18 @@ func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter,
|
||||
}
|
||||
|
||||
func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
img, err := ir.backend.GetImage(ctx, vars["name"], backend.GetImageOpts{Details: true})
|
||||
imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageInspect, err := ir.toImageInspect(img)
|
||||
if err != nil {
|
||||
return err
|
||||
// Make sure we output empty arrays instead of nil. While Go nil slice is functionally equivalent to an empty slice,
|
||||
// it matters for the JSON representation.
|
||||
if imageInspect.RepoTags == nil {
|
||||
imageInspect.RepoTags = []string{}
|
||||
}
|
||||
if imageInspect.RepoDigests == nil {
|
||||
imageInspect.RepoDigests = []string{}
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
@@ -330,74 +333,6 @@ func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWrite
|
||||
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
||||
}
|
||||
|
||||
func (ir *imageRouter) toImageInspect(img *image.Image) (*imagetypes.InspectResponse, error) {
|
||||
var repoTags, repoDigests []string
|
||||
for _, ref := range img.Details.References {
|
||||
switch ref.(type) {
|
||||
case reference.NamedTagged:
|
||||
repoTags = append(repoTags, reference.FamiliarString(ref))
|
||||
case reference.Canonical:
|
||||
repoDigests = append(repoDigests, reference.FamiliarString(ref))
|
||||
}
|
||||
}
|
||||
|
||||
comment := img.Comment
|
||||
if len(comment) == 0 && len(img.History) > 0 {
|
||||
comment = img.History[len(img.History)-1].Comment
|
||||
}
|
||||
|
||||
// Make sure we output empty arrays instead of nil.
|
||||
if repoTags == nil {
|
||||
repoTags = []string{}
|
||||
}
|
||||
if repoDigests == nil {
|
||||
repoDigests = []string{}
|
||||
}
|
||||
|
||||
var created string
|
||||
if img.Created != nil {
|
||||
created = img.Created.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
return &imagetypes.InspectResponse{
|
||||
ID: img.ID().String(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Parent: img.Parent.String(),
|
||||
Comment: comment,
|
||||
Created: created,
|
||||
Container: img.Container, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
ContainerConfig: &img.ContainerConfig, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
DockerVersion: img.DockerVersion,
|
||||
Author: img.Author,
|
||||
Config: img.Config,
|
||||
Architecture: img.Architecture,
|
||||
Variant: img.Variant,
|
||||
Os: img.OperatingSystem(),
|
||||
OsVersion: img.OSVersion,
|
||||
Size: img.Details.Size,
|
||||
GraphDriver: storage.DriverData{
|
||||
Name: img.Details.Driver,
|
||||
Data: img.Details.Metadata,
|
||||
},
|
||||
RootFS: rootFSToAPIType(img.RootFS),
|
||||
Metadata: imagetypes.Metadata{
|
||||
LastTagTime: img.Details.LastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func rootFSToAPIType(rootfs *image.RootFS) imagetypes.RootFS {
|
||||
var layers []string
|
||||
for _, l := range rootfs.DiffIDs {
|
||||
layers = append(layers, l.String())
|
||||
}
|
||||
return imagetypes.RootFS{
|
||||
Type: rootfs.Type,
|
||||
Layers: layers,
|
||||
}
|
||||
}
|
||||
|
||||
func (ir *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
|
||||
|
||||
@@ -141,9 +141,11 @@ type CreateImageConfig struct {
|
||||
// from the backend.
|
||||
type GetImageOpts struct {
|
||||
Platform *ocispec.Platform
|
||||
Details bool
|
||||
}
|
||||
|
||||
// ImageInspectOpts holds parameters to inspect an image.
|
||||
type ImageInspectOpts struct{}
|
||||
|
||||
// CommitConfig is the configuration for creating an image as part of a build.
|
||||
type CommitConfig struct {
|
||||
Author string
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
@@ -23,7 +21,6 @@ import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
var errInconsistentData error = errors.New("consistency error: data changed during operation, retry")
|
||||
@@ -44,8 +41,8 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options bac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ociImage := presentImages[0]
|
||||
|
||||
ociImage := presentImages[0]
|
||||
img := dockerOciImageToDockerImagePartial(image.ID(desc.Target.Digest), ociImage)
|
||||
|
||||
parent, err := i.getImageLabelByDigest(ctx, desc.Target.Digest, imageLabelClassicBuilderParent)
|
||||
@@ -55,68 +52,6 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options bac
|
||||
img.Parent = image.ID(parent)
|
||||
}
|
||||
|
||||
if options.Details {
|
||||
lastUpdated := time.Unix(0, 0)
|
||||
size, err := i.size(ctx, desc.Target, platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tagged, err := i.images.List(ctx, "target.digest=="+desc.Target.Digest.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Usually each image will result in 2 references (named and digested).
|
||||
refs := make([]reference.Named, 0, len(tagged)*2)
|
||||
for _, i := range tagged {
|
||||
if i.UpdatedAt.After(lastUpdated) {
|
||||
lastUpdated = i.UpdatedAt
|
||||
}
|
||||
if isDanglingImage(i) {
|
||||
if len(tagged) > 1 {
|
||||
// This is unexpected - dangling image should be deleted
|
||||
// as soon as another image with the same target is created.
|
||||
// Log a warning, but don't error out the whole operation.
|
||||
log.G(ctx).WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
name, err := reference.ParseNamed(i.Name)
|
||||
if err != nil {
|
||||
// This is inconsistent with `docker image ls` which will
|
||||
// still include the malformed name in RepoTags.
|
||||
log.G(ctx).WithField("name", name).WithError(err).Error("failed to parse image name as reference")
|
||||
continue
|
||||
}
|
||||
refs = append(refs, name)
|
||||
|
||||
if _, ok := name.(reference.Digested); ok {
|
||||
// Image name already contains a digest, so no need to create a digested reference.
|
||||
continue
|
||||
}
|
||||
|
||||
digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest)
|
||||
if err != nil {
|
||||
// This could only happen if digest is invalid, but considering that
|
||||
// we get it from the Descriptor it's highly unlikely.
|
||||
// Log error just in case.
|
||||
log.G(ctx).WithError(err).Error("failed to create digested reference")
|
||||
continue
|
||||
}
|
||||
refs = append(refs, digested)
|
||||
}
|
||||
|
||||
img.Details = &image.Details{
|
||||
References: refs,
|
||||
Size: size,
|
||||
Metadata: nil,
|
||||
Driver: i.snapshotter,
|
||||
LastUpdated: lastUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -231,34 +166,6 @@ func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, opt
|
||||
return nil, errdefs.NotFound(errors.New("failed to find manifest"))
|
||||
}
|
||||
|
||||
// size returns the total size of the image's packed resources.
|
||||
func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform platforms.MatchComparer) (int64, error) {
|
||||
var size atomic.Int64
|
||||
|
||||
cs := i.content
|
||||
handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)
|
||||
|
||||
var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := handler(ctx, desc)
|
||||
if err != nil {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
size.Add(desc.Size)
|
||||
|
||||
return children, nil
|
||||
}
|
||||
|
||||
l := semaphore.NewWeighted(3)
|
||||
if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return size.Load(), nil
|
||||
}
|
||||
|
||||
// resolveDescriptor searches for a descriptor based on the given
|
||||
// reference or identifier. Returns the descriptor of
|
||||
// the image, which could be a manifest list, manifest, or config.
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
containerdimages "github.com/containerd/containerd/images"
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/storage"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
||||
img, err := i.GetImage(ctx, refOrID, backend.GetImageOpts{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastUpdated := time.Unix(0, 0)
|
||||
|
||||
tagged, err := i.images.List(ctx, "target.digest=="+img.ImageID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This could happen only if the image was deleted after the GetImage call above.
|
||||
if len(tagged) == 0 {
|
||||
return nil, errInconsistentData
|
||||
}
|
||||
|
||||
platform := matchAllWithPreference(platforms.Default())
|
||||
|
||||
size, err := i.size(ctx, tagged[0].Target, platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imgDgst := tagged[0].Target.Digest
|
||||
|
||||
repoTags := make([]string, 0, len(tagged))
|
||||
repoDigests := make([]string, 0, len(tagged))
|
||||
for _, i := range tagged {
|
||||
if i.UpdatedAt.After(lastUpdated) {
|
||||
lastUpdated = i.UpdatedAt
|
||||
}
|
||||
if isDanglingImage(i) {
|
||||
if len(tagged) > 1 {
|
||||
// This is unexpected - dangling image should be deleted
|
||||
// as soon as another image with the same target is created.
|
||||
// Log a warning, but don't error out the whole operation.
|
||||
log.G(ctx).WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
name, err := reference.ParseNamed(i.Name)
|
||||
if err != nil {
|
||||
log.G(ctx).WithField("name", name).WithError(err).Error("failed to parse image name as reference")
|
||||
// Include the malformed name in RepoTags to be consistent with `docker image ls`.
|
||||
repoTags = append(repoTags, i.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
repoTags = append(repoTags, reference.FamiliarString(name))
|
||||
if _, ok := name.(reference.Digested); ok {
|
||||
repoDigests = append(repoDigests, reference.FamiliarString(name))
|
||||
// Image name is a digested reference already, so no need to create a digested reference.
|
||||
continue
|
||||
}
|
||||
|
||||
digested, err := reference.WithDigest(reference.TrimNamed(name), imgDgst)
|
||||
if err != nil {
|
||||
// This could only happen if digest is invalid, but considering that
|
||||
// we get it from the Descriptor it's highly unlikely.
|
||||
// Log error just in case.
|
||||
log.G(ctx).WithError(err).Error("failed to create digested reference")
|
||||
continue
|
||||
}
|
||||
repoDigests = append(repoDigests, reference.FamiliarString(digested))
|
||||
}
|
||||
|
||||
var comment string
|
||||
if len(comment) == 0 && len(img.History) > 0 {
|
||||
comment = img.History[len(img.History)-1].Comment
|
||||
}
|
||||
|
||||
var created string
|
||||
if img.Created != nil {
|
||||
created = img.Created.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
var layers []string
|
||||
for _, layer := range img.RootFS.DiffIDs {
|
||||
layers = append(layers, layer.String())
|
||||
}
|
||||
|
||||
return &imagetypes.InspectResponse{
|
||||
ID: img.ImageID(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Parent: img.Parent.String(),
|
||||
Comment: comment,
|
||||
Created: created,
|
||||
DockerVersion: "",
|
||||
Author: img.Author,
|
||||
Config: img.Config,
|
||||
Architecture: img.Architecture,
|
||||
Variant: img.Variant,
|
||||
Os: img.OS,
|
||||
OsVersion: img.OSVersion,
|
||||
Size: size,
|
||||
GraphDriver: storage.DriverData{
|
||||
Name: i.snapshotter,
|
||||
Data: nil,
|
||||
},
|
||||
RootFS: imagetypes.RootFS{
|
||||
Type: img.RootFS.Type,
|
||||
Layers: layers,
|
||||
},
|
||||
Metadata: imagetypes.Metadata{
|
||||
LastTagTime: lastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// size returns the total size of the image's packed resources.
|
||||
func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform platforms.MatchComparer) (int64, error) {
|
||||
var size atomic.Int64
|
||||
|
||||
cs := i.content
|
||||
handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)
|
||||
|
||||
var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := handler(ctx, desc)
|
||||
if err != nil {
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
size.Add(desc.Size)
|
||||
|
||||
return children, nil
|
||||
}
|
||||
|
||||
l := semaphore.NewWeighted(3)
|
||||
if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return size.Load(), nil
|
||||
}
|
||||
@@ -43,6 +43,7 @@ type ImageService interface {
|
||||
ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error)
|
||||
CommitImage(ctx context.Context, c backend.CommitConfig) (image.ID, error)
|
||||
SquashImage(id, parent string) (string, error)
|
||||
ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetype.InspectResponse, error)
|
||||
|
||||
// Containerd related methods
|
||||
|
||||
|
||||
+2
-40
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@@ -163,49 +162,12 @@ func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.I
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetImage returns an image corresponding to the image referred to by refOrID.
|
||||
func (i *ImageService) GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*image.Image, error) {
|
||||
img, err := i.getImage(ctx, refOrID, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Details {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdated, err := i.imageStore.GetLastUpdated(img.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img.Details = &image.Details{
|
||||
References: i.referenceStore.References(img.ID().Digest()),
|
||||
Size: size,
|
||||
Metadata: layerMetadata,
|
||||
Driver: i.layerStore.DriverName(),
|
||||
LastUpdated: lastUpdated,
|
||||
}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options backend.GetImageOpts) (*ocispec.Descriptor, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (i *ImageService) getImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (retImg *image.Image, retErr error) {
|
||||
// GetImage returns an image corresponding to the image referred to by refOrID.
|
||||
func (i *ImageService) GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (retImg *image.Image, retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil || retImg == nil || options.Platform == nil {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/storage"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
)
|
||||
|
||||
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
||||
img, err := i.GetImage(ctx, refOrID, backend.GetImageOpts{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size, layerMetadata, err := i.getLayerSizeAndMetadata(img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastUpdated, err := i.imageStore.GetLastUpdated(img.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repoTags, repoDigests []string
|
||||
for _, ref := range i.referenceStore.References(img.ID().Digest()) {
|
||||
switch ref.(type) {
|
||||
case reference.NamedTagged:
|
||||
repoTags = append(repoTags, reference.FamiliarString(ref))
|
||||
case reference.Canonical:
|
||||
repoDigests = append(repoDigests, reference.FamiliarString(ref))
|
||||
}
|
||||
}
|
||||
|
||||
comment := img.Comment
|
||||
if len(comment) == 0 && len(img.History) > 0 {
|
||||
comment = img.History[len(img.History)-1].Comment
|
||||
}
|
||||
|
||||
var created string
|
||||
if img.Created != nil {
|
||||
created = img.Created.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
var layers []string
|
||||
for _, l := range img.RootFS.DiffIDs {
|
||||
layers = append(layers, l.String())
|
||||
}
|
||||
|
||||
return &imagetypes.InspectResponse{
|
||||
ID: img.ID().String(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Parent: img.Parent.String(),
|
||||
Comment: comment,
|
||||
Created: created,
|
||||
Container: img.Container, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
ContainerConfig: &img.ContainerConfig, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
|
||||
DockerVersion: img.DockerVersion,
|
||||
Author: img.Author,
|
||||
Config: img.Config,
|
||||
Architecture: img.Architecture,
|
||||
Variant: img.Variant,
|
||||
Os: img.OperatingSystem(),
|
||||
OsVersion: img.OSVersion,
|
||||
Size: size,
|
||||
GraphDriver: storage.DriverData{
|
||||
Name: i.layerStore.DriverName(),
|
||||
Data: layerMetadata,
|
||||
},
|
||||
RootFS: imagetypes.RootFS{
|
||||
Type: img.RootFS.Type,
|
||||
Layers: layers,
|
||||
},
|
||||
Metadata: imagetypes.Metadata{
|
||||
LastTagTime: lastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) getLayerSizeAndMetadata(img *image.Image) (int64, map[string]string, error) {
|
||||
var size int64
|
||||
var layerMetadata map[string]string
|
||||
layerID := img.RootFS.ChainID()
|
||||
if layerID != "" {
|
||||
l, err := i.layerStore.Get(layerID)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer layer.ReleaseAndLog(i.layerStore, l)
|
||||
size = l.Size()
|
||||
layerMetadata, err = l.Metadata()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
return size, layerMetadata, nil
|
||||
}
|
||||
Reference in new issue
Block a user