github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/containerd/image_list.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/containerd/containerd"
     7  	"github.com/docker/distribution/reference"
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/api/types/filters"
    10  	"github.com/opencontainers/go-digest"
    11  	"github.com/opencontainers/image-spec/identity"
    12  )
    13  
    14  var acceptedImageFilterTags = map[string]bool{
    15  	"dangling":  false, // TODO(thaJeztah): implement "dangling" filter: see https://github.com/moby/moby/issues/43846
    16  	"label":     true,
    17  	"before":    true,
    18  	"since":     true,
    19  	"reference": false, // TODO(thaJeztah): implement "reference" filter: see https://github.com/moby/moby/issues/43847
    20  }
    21  
    22  // Images returns a filtered list of images.
    23  //
    24  // TODO(thaJeztah): sort the results by created (descending); see https://github.com/moby/moby/issues/43848
    25  // TODO(thaJeztah): implement opts.ContainerCount (used for docker system df); see https://github.com/moby/moby/issues/43853
    26  // TODO(thaJeztah): add labels to results; see https://github.com/moby/moby/issues/43852
    27  // TODO(thaJeztah): verify behavior of `RepoDigests` and `RepoTags` for images without (untagged) or multiple tags; see https://github.com/moby/moby/issues/43861
    28  // TODO(thaJeztah): verify "Size" vs "VirtualSize" in images; see https://github.com/moby/moby/issues/43862
    29  func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) {
    30  	if err := opts.Filters.Validate(acceptedImageFilterTags); err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	filter, err := i.setupFilters(ctx, opts.Filters)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	imgs, err := i.client.ListImages(ctx)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	snapshotter := i.client.SnapshotService(i.snapshotter)
    45  	sizeCache := make(map[digest.Digest]int64)
    46  	snapshotSizeFn := func(d digest.Digest) (int64, error) {
    47  		if s, ok := sizeCache[d]; ok {
    48  			return s, nil
    49  		}
    50  		usage, err := snapshotter.Usage(ctx, d.String())
    51  		if err != nil {
    52  			return 0, err
    53  		}
    54  		sizeCache[d] = usage.Size
    55  		return usage.Size, nil
    56  	}
    57  
    58  	var (
    59  		summaries = make([]*types.ImageSummary, 0, len(imgs))
    60  		root      []*[]digest.Digest
    61  		layers    map[digest.Digest]int
    62  	)
    63  	if opts.SharedSize {
    64  		root = make([]*[]digest.Digest, len(imgs))
    65  		layers = make(map[digest.Digest]int)
    66  	}
    67  	for n, img := range imgs {
    68  		if !filter(img) {
    69  			continue
    70  		}
    71  
    72  		diffIDs, err := img.RootFS(ctx)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		chainIDs := identity.ChainIDs(diffIDs)
    77  		if opts.SharedSize {
    78  			root[n] = &chainIDs
    79  			for _, id := range chainIDs {
    80  				layers[id] = layers[id] + 1
    81  			}
    82  		}
    83  
    84  		size, err := img.Size(ctx)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  
    89  		virtualSize, err := computeVirtualSize(chainIDs, snapshotSizeFn)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  
    94  		summaries = append(summaries, &types.ImageSummary{
    95  			ParentID:    "",
    96  			ID:          img.Target().Digest.String(),
    97  			Created:     img.Metadata().CreatedAt.Unix(),
    98  			RepoDigests: []string{img.Name() + "@" + img.Target().Digest.String()}, // "hello-world@sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38"},
    99  			RepoTags:    []string{img.Name()},
   100  			Size:        size,
   101  			VirtualSize: virtualSize,
   102  			// -1 indicates that the value has not been set (avoids ambiguity
   103  			// between 0 (default) and "not set". We cannot use a pointer (nil)
   104  			// for this, as the JSON representation uses "omitempty", which would
   105  			// consider both "0" and "nil" to be "empty".
   106  			SharedSize: -1,
   107  			Containers: -1,
   108  		})
   109  	}
   110  
   111  	if opts.SharedSize {
   112  		for n, chainIDs := range root {
   113  			sharedSize, err := computeSharedSize(*chainIDs, layers, snapshotSizeFn)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  			summaries[n].SharedSize = sharedSize
   118  		}
   119  	}
   120  
   121  	return summaries, nil
   122  }
   123  
   124  type imageFilterFunc func(image containerd.Image) bool
   125  
   126  // setupFilters constructs an imageFilterFunc from the given imageFilters.
   127  //
   128  // TODO(thaJeztah): reimplement filters using containerd filters: see https://github.com/moby/moby/issues/43845
   129  func (i *ImageService) setupFilters(ctx context.Context, imageFilters filters.Args) (imageFilterFunc, error) {
   130  	var fltrs []imageFilterFunc
   131  	err := imageFilters.WalkValues("before", func(value string) error {
   132  		ref, err := reference.ParseDockerRef(value)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		img, err := i.client.GetImage(ctx, ref.String())
   137  		if img != nil {
   138  			t := img.Metadata().CreatedAt
   139  			fltrs = append(fltrs, func(image containerd.Image) bool {
   140  				created := image.Metadata().CreatedAt
   141  				return created.Equal(t) || created.After(t)
   142  			})
   143  		}
   144  		return err
   145  	})
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	err = imageFilters.WalkValues("since", func(value string) error {
   151  		ref, err := reference.ParseDockerRef(value)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		img, err := i.client.GetImage(ctx, ref.String())
   156  		if img != nil {
   157  			t := img.Metadata().CreatedAt
   158  			fltrs = append(fltrs, func(image containerd.Image) bool {
   159  				created := image.Metadata().CreatedAt
   160  				return created.Equal(t) || created.Before(t)
   161  			})
   162  		}
   163  		return err
   164  	})
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	if imageFilters.Contains("label") {
   170  		fltrs = append(fltrs, func(image containerd.Image) bool {
   171  			return imageFilters.MatchKVList("label", image.Labels())
   172  		})
   173  	}
   174  	return func(image containerd.Image) bool {
   175  		for _, filter := range fltrs {
   176  			if !filter(image) {
   177  				return false
   178  			}
   179  		}
   180  		return true
   181  	}, nil
   182  }
   183  
   184  func computeVirtualSize(chainIDs []digest.Digest, sizeFn func(d digest.Digest) (int64, error)) (int64, error) {
   185  	var virtualSize int64
   186  	for _, chainID := range chainIDs {
   187  		size, err := sizeFn(chainID)
   188  		if err != nil {
   189  			return virtualSize, err
   190  		}
   191  		virtualSize += size
   192  	}
   193  	return virtualSize, nil
   194  }
   195  
   196  func computeSharedSize(chainIDs []digest.Digest, layers map[digest.Digest]int, sizeFn func(d digest.Digest) (int64, error)) (int64, error) {
   197  	var sharedSize int64
   198  	for _, chainID := range chainIDs {
   199  		if layers[chainID] == 1 {
   200  			continue
   201  		}
   202  		size, err := sizeFn(chainID)
   203  		if err != nil {
   204  			return 0, err
   205  		}
   206  		sharedSize += size
   207  	}
   208  	return sharedSize, nil
   209  }