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 }