mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
199 lines
5.5 KiB
Go
199 lines
5.5 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
c8dimages "github.com/containerd/containerd/v2/core/images"
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/containerd/log"
|
|
"github.com/containerd/platforms"
|
|
"github.com/distribution/reference"
|
|
imagespec "github.com/moby/docker-image-spec/specs-go/v1"
|
|
imagetypes "github.com/moby/moby/api/types/image"
|
|
"github.com/moby/moby/v2/daemon/server/imagebackend"
|
|
"github.com/moby/moby/v2/internal/sliceutil"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"golang.org/x/sync/semaphore"
|
|
)
|
|
|
|
func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts imagebackend.ImageInspectOpts) (*imagetypes.InspectResponse, error) {
|
|
requestedPlatform := opts.Platform
|
|
|
|
c8dImg, err := i.resolveImage(ctx, refOrID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
target := c8dImg.Target
|
|
tagged, err := i.images.List(ctx, "target.digest=="+target.Digest.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// This could happen only if the image was deleted after the resolveImage call above.
|
|
if len(tagged) == 0 {
|
|
return nil, errInconsistentData
|
|
}
|
|
|
|
lastUpdated := time.Unix(0, 0)
|
|
for _, i := range tagged {
|
|
if i.UpdatedAt.After(lastUpdated) {
|
|
lastUpdated = i.UpdatedAt
|
|
}
|
|
}
|
|
|
|
platform := i.matchRequestedOrDefault(platforms.OnlyStrict, requestedPlatform)
|
|
size, err := i.size(ctx, target, platform)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
multi, err := i.multiPlatformSummary(ctx, c8dImg, platform)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if multi.Best == nil && requestedPlatform != nil {
|
|
return nil, &errPlatformNotFound{
|
|
imageRef: refOrID,
|
|
wanted: *requestedPlatform,
|
|
}
|
|
}
|
|
|
|
var img imagespec.DockerOCIImage
|
|
if multi.Best != nil {
|
|
if err := multi.Best.ReadConfig(ctx, &img); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
parent, err := i.getImageLabelByDigest(ctx, target.Digest, imageLabelClassicBuilderParent)
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Warn("failed to determine Parent property")
|
|
}
|
|
|
|
var manifests []imagetypes.ManifestSummary
|
|
if opts.Manifests {
|
|
manifests = multi.Manifests
|
|
}
|
|
|
|
repoTags, repoDigests := collectRepoTagsAndDigests(ctx, tagged)
|
|
|
|
if requestedPlatform != nil {
|
|
target = multi.Best.Target()
|
|
}
|
|
|
|
resp := &imagetypes.InspectResponse{
|
|
ID: target.Digest.String(),
|
|
RepoTags: repoTags,
|
|
Descriptor: &target,
|
|
RepoDigests: repoDigests,
|
|
Parent: parent,
|
|
DockerVersion: "",
|
|
Size: size,
|
|
Manifests: manifests,
|
|
Metadata: imagetypes.Metadata{
|
|
LastTagTime: lastUpdated,
|
|
},
|
|
}
|
|
|
|
if multi.Best != nil {
|
|
imgConfig := img.Config
|
|
resp.Author = img.Author
|
|
resp.Config = &imgConfig
|
|
resp.Architecture = img.Architecture
|
|
resp.Variant = img.Variant
|
|
resp.Os = img.OS
|
|
resp.OsVersion = img.OSVersion
|
|
|
|
if len(img.History) > 0 {
|
|
resp.Comment = img.History[len(img.History)-1].Comment
|
|
}
|
|
|
|
if img.Created != nil {
|
|
resp.Created = img.Created.Format(time.RFC3339Nano)
|
|
}
|
|
|
|
resp.RootFS = imagetypes.RootFS{
|
|
Type: img.RootFS.Type,
|
|
}
|
|
for _, layer := range img.RootFS.DiffIDs {
|
|
resp.RootFS.Layers = append(resp.RootFS.Layers, layer.String())
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func collectRepoTagsAndDigests(ctx context.Context, tagged []c8dimages.Image) (repoTags []string, repoDigests []string) {
|
|
repoTags = make([]string, 0, len(tagged))
|
|
repoDigests = make([]string, 0, len(tagged))
|
|
for _, img := range tagged {
|
|
if isDanglingImage(img) {
|
|
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(img.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, img.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), img.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
|
|
}
|
|
repoDigests = append(repoDigests, reference.FamiliarString(digested))
|
|
}
|
|
return sliceutil.Dedup(repoTags), sliceutil.Dedup(repoDigests)
|
|
}
|
|
|
|
// 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 := c8dimages.LimitManifests(c8dimages.ChildrenHandler(cs), platform, 1)
|
|
|
|
var wh c8dimages.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 := c8dimages.Dispatch(ctx, wh, l, desc); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return size.Load(), nil
|
|
}
|