Skip to content

Commit cd11843

Browse files
committed
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 <[email protected]>
1 parent 2b1097f commit cd11843

8 files changed

Lines changed: 278 additions & 208 deletions

File tree

api/server/router/image/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type imageBackend interface {
2626
ImageHistory(ctx context.Context, imageName string) ([]*image.HistoryResponseItem, error)
2727
Images(ctx context.Context, opts image.ListOptions) ([]*image.Summary, error)
2828
GetImage(ctx context.Context, refOrID string, options backend.GetImageOpts) (*dockerimage.Image, error)
29+
ImageInspect(ctx context.Context, refOrID string, options backend.ImageInspectOpts) (*image.InspectResponse, error)
2930
TagImage(ctx context.Context, id dockerimage.ID, newRef reference.Named) error
3031
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*image.PruneReport, error)
3132
}

api/server/router/image/image_routes.go

Lines changed: 8 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/docker/docker/api/types/filters"
1919
imagetypes "github.com/docker/docker/api/types/image"
2020
"github.com/docker/docker/api/types/registry"
21-
"github.com/docker/docker/api/types/storage"
2221
"github.com/docker/docker/api/types/versions"
2322
"github.com/docker/docker/builder/remotecontext"
2423
"github.com/docker/docker/dockerversion"
@@ -303,14 +302,18 @@ func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter,
303302
}
304303

305304
func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
306-
img, err := ir.backend.GetImage(ctx, vars["name"], backend.GetImageOpts{Details: true})
305+
imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{})
307306
if err != nil {
308307
return err
309308
}
310309

311-
imageInspect, err := ir.toImageInspect(img)
312-
if err != nil {
313-
return err
310+
// Make sure we output empty arrays instead of nil. While Go nil slice is functionally equivalent to an empty slice,
311+
// it matters for the JSON representation.
312+
if imageInspect.RepoTags == nil {
313+
imageInspect.RepoTags = []string{}
314+
}
315+
if imageInspect.RepoDigests == nil {
316+
imageInspect.RepoDigests = []string{}
314317
}
315318

316319
version := httputils.VersionFromContext(ctx)
@@ -330,74 +333,6 @@ func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWrite
330333
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
331334
}
332335

333-
func (ir *imageRouter) toImageInspect(img *image.Image) (*imagetypes.InspectResponse, error) {
334-
var repoTags, repoDigests []string
335-
for _, ref := range img.Details.References {
336-
switch ref.(type) {
337-
case reference.NamedTagged:
338-
repoTags = append(repoTags, reference.FamiliarString(ref))
339-
case reference.Canonical:
340-
repoDigests = append(repoDigests, reference.FamiliarString(ref))
341-
}
342-
}
343-
344-
comment := img.Comment
345-
if len(comment) == 0 && len(img.History) > 0 {
346-
comment = img.History[len(img.History)-1].Comment
347-
}
348-
349-
// Make sure we output empty arrays instead of nil.
350-
if repoTags == nil {
351-
repoTags = []string{}
352-
}
353-
if repoDigests == nil {
354-
repoDigests = []string{}
355-
}
356-
357-
var created string
358-
if img.Created != nil {
359-
created = img.Created.Format(time.RFC3339Nano)
360-
}
361-
362-
return &imagetypes.InspectResponse{
363-
ID: img.ID().String(),
364-
RepoTags: repoTags,
365-
RepoDigests: repoDigests,
366-
Parent: img.Parent.String(),
367-
Comment: comment,
368-
Created: created,
369-
Container: img.Container, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
370-
ContainerConfig: &img.ContainerConfig, //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.45.
371-
DockerVersion: img.DockerVersion,
372-
Author: img.Author,
373-
Config: img.Config,
374-
Architecture: img.Architecture,
375-
Variant: img.Variant,
376-
Os: img.OperatingSystem(),
377-
OsVersion: img.OSVersion,
378-
Size: img.Details.Size,
379-
GraphDriver: storage.DriverData{
380-
Name: img.Details.Driver,
381-
Data: img.Details.Metadata,
382-
},
383-
RootFS: rootFSToAPIType(img.RootFS),
384-
Metadata: imagetypes.Metadata{
385-
LastTagTime: img.Details.LastUpdated,
386-
},
387-
}, nil
388-
}
389-
390-
func rootFSToAPIType(rootfs *image.RootFS) imagetypes.RootFS {
391-
var layers []string
392-
for _, l := range rootfs.DiffIDs {
393-
layers = append(layers, l.String())
394-
}
395-
return imagetypes.RootFS{
396-
Type: rootfs.Type,
397-
Layers: layers,
398-
}
399-
}
400-
401336
func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
402337
if err := httputils.ParseForm(r); err != nil {
403338
return err

api/types/backend/backend.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,11 @@ type CreateImageConfig struct {
141141
// from the backend.
142142
type GetImageOpts struct {
143143
Platform *ocispec.Platform
144-
Details bool
145144
}
146145

146+
// ImageInspectOpts holds parameters to inspect an image.
147+
type ImageInspectOpts struct{}
148+
147149
// CommitConfig is the configuration for creating an image as part of a build.
148150
type CommitConfig struct {
149151
Author string

daemon/containerd/image.go

Lines changed: 1 addition & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77
"sort"
88
"strconv"
99
"strings"
10-
"sync/atomic"
11-
"time"
1210

1311
containerdimages "github.com/containerd/containerd/images"
1412
cerrdefs "github.com/containerd/errdefs"
@@ -23,7 +21,6 @@ import (
2321
"github.com/opencontainers/go-digest"
2422
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2523
"github.com/pkg/errors"
26-
"golang.org/x/sync/semaphore"
2724
)
2825

2926
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
4441
if err != nil {
4542
return nil, err
4643
}
47-
ociImage := presentImages[0]
4844

45+
ociImage := presentImages[0]
4946
img := dockerOciImageToDockerImagePartial(image.ID(desc.Target.Digest), ociImage)
5047

5148
parent, err := i.getImageLabelByDigest(ctx, desc.Target.Digest, imageLabelClassicBuilderParent)
@@ -55,68 +52,6 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options bac
5552
img.Parent = image.ID(parent)
5653
}
5754

58-
if options.Details {
59-
lastUpdated := time.Unix(0, 0)
60-
size, err := i.size(ctx, desc.Target, platform)
61-
if err != nil {
62-
return nil, err
63-
}
64-
65-
tagged, err := i.images.List(ctx, "target.digest=="+desc.Target.Digest.String())
66-
if err != nil {
67-
return nil, err
68-
}
69-
70-
// Usually each image will result in 2 references (named and digested).
71-
refs := make([]reference.Named, 0, len(tagged)*2)
72-
for _, i := range tagged {
73-
if i.UpdatedAt.After(lastUpdated) {
74-
lastUpdated = i.UpdatedAt
75-
}
76-
if isDanglingImage(i) {
77-
if len(tagged) > 1 {
78-
// This is unexpected - dangling image should be deleted
79-
// as soon as another image with the same target is created.
80-
// Log a warning, but don't error out the whole operation.
81-
log.G(ctx).WithField("refs", tagged).Warn("multiple images have the same target, but one of them is still dangling")
82-
}
83-
continue
84-
}
85-
86-
name, err := reference.ParseNamed(i.Name)
87-
if err != nil {
88-
// This is inconsistent with `docker image ls` which will
89-
// still include the malformed name in RepoTags.
90-
log.G(ctx).WithField("name", name).WithError(err).Error("failed to parse image name as reference")
91-
continue
92-
}
93-
refs = append(refs, name)
94-
95-
if _, ok := name.(reference.Digested); ok {
96-
// Image name already contains a digest, so no need to create a digested reference.
97-
continue
98-
}
99-
100-
digested, err := reference.WithDigest(reference.TrimNamed(name), desc.Target.Digest)
101-
if err != nil {
102-
// This could only happen if digest is invalid, but considering that
103-
// we get it from the Descriptor it's highly unlikely.
104-
// Log error just in case.
105-
log.G(ctx).WithError(err).Error("failed to create digested reference")
106-
continue
107-
}
108-
refs = append(refs, digested)
109-
}
110-
111-
img.Details = &image.Details{
112-
References: refs,
113-
Size: size,
114-
Metadata: nil,
115-
Driver: i.snapshotter,
116-
LastUpdated: lastUpdated,
117-
}
118-
}
119-
12055
return img, nil
12156
}
12257

@@ -231,34 +166,6 @@ func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, opt
231166
return nil, errdefs.NotFound(errors.New("failed to find manifest"))
232167
}
233168

234-
// size returns the total size of the image's packed resources.
235-
func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform platforms.MatchComparer) (int64, error) {
236-
var size atomic.Int64
237-
238-
cs := i.content
239-
handler := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)
240-
241-
var wh containerdimages.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
242-
children, err := handler(ctx, desc)
243-
if err != nil {
244-
if !cerrdefs.IsNotFound(err) {
245-
return nil, err
246-
}
247-
}
248-
249-
size.Add(desc.Size)
250-
251-
return children, nil
252-
}
253-
254-
l := semaphore.NewWeighted(3)
255-
if err := containerdimages.Dispatch(ctx, wh, l, desc); err != nil {
256-
return 0, err
257-
}
258-
259-
return size.Load(), nil
260-
}
261-
262169
// resolveDescriptor searches for a descriptor based on the given
263170
// reference or identifier. Returns the descriptor of
264171
// the image, which could be a manifest list, manifest, or config.

0 commit comments

Comments
 (0)