Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for shared-size parameter for images queries #42531

Merged
merged 2 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/server/router/image/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Backend interface {
type imageBackend interface {
ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error)
ImageHistory(imageName string) ([]*image.HistoryResponseItem, error)
Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error)
Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error)
LookupImage(name string) (*types.ImageInspect, error)
TagImage(imageName, repository, tag string) (string, error)
ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error)
Expand Down
13 changes: 12 additions & 1 deletion api/server/router/image/image_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,24 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,

version := httputils.VersionFromContext(ctx)
if versions.LessThan(version, "1.41") {
// NOTE: filter is a shell glob string applied to repository names.
filterParam := r.Form.Get("filter")
if filterParam != "" {
imageFilters.Add("reference", filterParam)
}
}

images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false)
var sharedSize bool
if versions.GreaterThanOrEqualTo(version, "1.42") {
// NOTE: Support for the "shared-size" parameter was added in API 1.42.
sharedSize = httputils.BoolValue(r, "shared-size")
rvolosatovs marked this conversation as resolved.
Show resolved Hide resolved
}

images, err := s.backend.Images(ctx, types.ImageListOptions{
rvolosatovs marked this conversation as resolved.
Show resolved Hide resolved
rvolosatovs marked this conversation as resolved.
Show resolved Hide resolved
All: httputils.BoolValue(r, "all"),
Filters: imageFilters,
SharedSize: sharedSize,
})
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7202,6 +7202,11 @@ paths:
- `reference`=(`<image-name>[:<tag>]`)
- `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
type: "string"
- name: "shared-size"
rvolosatovs marked this conversation as resolved.
Show resolved Hide resolved
in: "query"
description: "Compute and show shared size as a `SharedSize` field on each image."
type: "boolean"
default: false
- name: "digests"
in: "query"
description: "Show digest information as a `RepoDigests` field on each image."
Expand Down
14 changes: 12 additions & 2 deletions api/types/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,20 @@ type ImageImportOptions struct {
Platform string // Platform is the target platform of the image
}

// ImageListOptions holds parameters to filter the list of images with.
// ImageListOptions holds parameters to list images with.
type ImageListOptions struct {
All bool
// All controls whether all images in the graph are filtered, or just
// the heads.
All bool

// Filters is a JSON-encoded set of filter arguments.
Filters filters.Args

// SharedSize indicates whether the shared size of images should be computed.
SharedSize bool

// ContainerCount indicates whether container count should be computed.
ContainerCount bool
}

// ImageLoadResponse returns information to the client about a load process.
Expand Down
6 changes: 5 additions & 1 deletion daemon/disk_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
}

// Get all top images with extra attributes
allImages, err := daemon.imageService.Images(filters.NewArgs(), false, true)
allImages, err := daemon.imageService.Images(ctx, types.ImageListOptions{
Filters: filters.NewArgs(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but wondering if we should change Filters to be a pointer, so that it can be left nil if no filtering is used

SharedSize: true,
ContainerCount: true,
})
if err != nil {
return nil, fmt.Errorf("failed to retrieve image list: %v", err)
}
Expand Down
53 changes: 27 additions & 26 deletions daemon/images/images.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package images // import "github.com/docker/docker/daemon/images"

import (
"context"
"encoding/json"
"fmt"
"sort"
Expand All @@ -10,7 +11,6 @@ import (

"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/container"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
Expand Down Expand Up @@ -38,38 +38,34 @@ func (i *ImageService) Map() map[image.ID]*image.Image {
return i.imageStore.Map()
}

// Images returns a filtered list of images. filterArgs is a JSON-encoded set
// of filter arguments which will be interpreted by api/types/filters.
// filter is a shell glob string applied to repository names. The argument
// named all controls whether all images in the graph are filtered, or just
// the heads.
func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
if err := imageFilters.Validate(acceptedImageFilterTags); err != nil {
// Images returns a filtered list of images.
func (i *ImageService) Images(_ context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) {
if err := opts.Filters.Validate(acceptedImageFilterTags); err != nil {
return nil, err
}

var danglingOnly bool
if imageFilters.Contains("dangling") {
if imageFilters.ExactMatch("dangling", "true") {
if opts.Filters.Contains("dangling") {
if opts.Filters.ExactMatch("dangling", "true") {
danglingOnly = true
} else if !imageFilters.ExactMatch("dangling", "false") {
return nil, invalidFilter{"dangling", imageFilters.Get("dangling")}
} else if !opts.Filters.ExactMatch("dangling", "false") {
return nil, invalidFilter{"dangling", opts.Filters.Get("dangling")}
}
}

var (
beforeFilter, sinceFilter *image.Image
err error
)
err = imageFilters.WalkValues("before", func(value string) error {
err = opts.Filters.WalkValues("before", func(value string) error {
beforeFilter, err = i.GetImage(value, nil)
return err
})
if err != nil {
return nil, err
}

err = imageFilters.WalkValues("since", func(value string) error {
err = opts.Filters.WalkValues("since", func(value string) error {
sinceFilter, err = i.GetImage(value, nil)
return err
})
Expand Down Expand Up @@ -102,13 +98,13 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
}
}

if imageFilters.Contains("label") {
if opts.Filters.Contains("label") {
// Very old image that do not have image.Config (or even labels)
if img.Config == nil {
continue
}
// We are now sure image.Config is not nil
if !imageFilters.MatchKVList("label", img.Config.Labels) {
if !opts.Filters.MatchKVList("label", img.Config.Labels) {
continue
}
}
Expand Down Expand Up @@ -142,10 +138,10 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
summary := newImageSummary(img, size)

for _, ref := range i.referenceStore.References(id.Digest()) {
if imageFilters.Contains("reference") {
if opts.Filters.Contains("reference") {
var found bool
var matchErr error
for _, pattern := range imageFilters.Get("reference") {
for _, pattern := range opts.Filters.Get("reference") {
found, matchErr = reference.FamiliarMatch(pattern, ref)
if matchErr != nil {
return nil, matchErr
Expand All @@ -166,13 +162,13 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
}
}
if summary.RepoDigests == nil && summary.RepoTags == nil {
if all || len(i.imageStore.Children(id)) == 0 {
if opts.All || len(i.imageStore.Children(id)) == 0 {

if imageFilters.Contains("dangling") && !danglingOnly {
if opts.Filters.Contains("dangling") && !danglingOnly {
// dangling=false case, so dangling image is not needed
continue
}
if imageFilters.Contains("reference") { // skip images with no references if filtering by reference
if opts.Filters.Contains("reference") { // skip images with no references if filtering by reference
continue
}
summary.RepoDigests = []string{"<none>@<none>"}
Expand All @@ -184,10 +180,9 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
continue
}

if withExtraAttrs {
// Lazily init summaryMap and allContainers
if summaryMap == nil {
summaryMap = make(map[*image.Image]*types.ImageSummary, len(selectedImages))
if opts.ContainerCount {
// Lazily init allContainers.
if allContainers == nil {
allContainers = i.containers.List()
}

Expand All @@ -200,13 +195,19 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
}
// NOTE: By default, Containers is -1, or "not set"
summary.Containers = containers
}

if opts.ContainerCount || opts.SharedSize {
// Lazily init summaryMap.
if summaryMap == nil {
summaryMap = make(map[*image.Image]*types.ImageSummary, len(selectedImages))
}
summaryMap[img] = summary
}
summaries = append(summaries, summary)
}

if withExtraAttrs {
if opts.SharedSize {
allLayers := i.layerStore.Map()
layerRefs := make(map[layer.ChainID]int, len(allLayers))

Expand Down
3 changes: 3 additions & 0 deletions docs/api/version-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ keywords: "API, Docker, rcli, REST, documentation"
was introduced in API 1.31 as part of an experimental feature, and no longer
used since API 1.40.
Use field `BuildCache` instead to track storage used by the builder component.
* `GET /images/json` now accepts query parameter `shared-size`. When set `true`,
images returned will include `SharedSize`, which provides the size on disk shared
with other images present on the system.

## v1.41 API changes

Expand Down