2022-07-18 10:51:49 +00:00
package containerd
import (
"context"
2022-07-06 07:58:10 +00:00
"github.com/containerd/containerd"
2022-07-06 15:46:39 +00:00
"github.com/docker/distribution/reference"
2022-07-18 10:51:49 +00:00
"github.com/docker/docker/api/types"
2022-07-06 15:46:39 +00:00
"github.com/docker/docker/api/types/filters"
2022-07-11 15:42:13 +00:00
"github.com/opencontainers/go-digest"
2022-07-06 07:58:10 +00:00
"github.com/opencontainers/image-spec/identity"
2022-07-18 10:51:49 +00:00
)
2022-07-21 09:01:10 +00:00
var acceptedImageFilterTags = map [ string ] bool {
"dangling" : false , // TODO(thaJeztah): implement "dangling" filter: see https://github.com/moby/moby/issues/43846
"label" : true ,
"before" : true ,
"since" : true ,
"reference" : false , // TODO(thaJeztah): implement "reference" filter: see https://github.com/moby/moby/issues/43847
}
2022-07-18 10:51:49 +00:00
// Images returns a filtered list of images.
2022-07-06 15:46:39 +00:00
//
// TODO(thaJeztah): sort the results by created (descending); see https://github.com/moby/moby/issues/43848
2022-07-25 10:17:23 +00:00
// TODO(thaJeztah): implement opts.ContainerCount (used for docker system df); see https://github.com/moby/moby/issues/43853
// TODO(thaJeztah): add labels to results; see https://github.com/moby/moby/issues/43852
// TODO(thaJeztah): verify behavior of `RepoDigests` and `RepoTags` for images without (untagged) or multiple tags; see https://github.com/moby/moby/issues/43861
// TODO(thaJeztah): verify "Size" vs "VirtualSize" in images; see https://github.com/moby/moby/issues/43862
2022-07-18 10:51:49 +00:00
func ( i * ImageService ) Images ( ctx context . Context , opts types . ImageListOptions ) ( [ ] * types . ImageSummary , error ) {
2022-07-21 09:01:10 +00:00
if err := opts . Filters . Validate ( acceptedImageFilterTags ) ; err != nil {
return nil , err
}
2022-07-06 15:46:39 +00:00
filter , err := i . setupFilters ( ctx , opts . Filters )
if err != nil {
return nil , err
}
2022-07-18 10:51:49 +00:00
imgs , err := i . client . ListImages ( ctx )
if err != nil {
return nil , err
}
2022-08-03 09:20:54 +00:00
snapshotter := i . client . SnapshotService ( i . snapshotter )
2022-07-11 15:42:13 +00:00
sizeCache := make ( map [ digest . Digest ] int64 )
snapshotSizeFn := func ( d digest . Digest ) ( int64 , error ) {
if s , ok := sizeCache [ d ] ; ok {
return s , nil
}
usage , err := snapshotter . Usage ( ctx , d . String ( ) )
if err != nil {
return 0 , err
}
sizeCache [ d ] = usage . Size
return usage . Size , nil
}
2022-07-06 07:58:10 +00:00
2022-07-11 15:42:13 +00:00
var (
summaries = make ( [ ] * types . ImageSummary , 0 , len ( imgs ) )
root [ ] * [ ] digest . Digest
layers map [ digest . Digest ] int
)
if opts . SharedSize {
root = make ( [ ] * [ ] digest . Digest , len ( imgs ) )
layers = make ( map [ digest . Digest ] int )
}
for n , img := range imgs {
2022-07-06 15:46:39 +00:00
if ! filter ( img ) {
continue
}
2022-07-11 15:42:13 +00:00
diffIDs , err := img . RootFS ( ctx )
if err != nil {
return nil , err
}
chainIDs := identity . ChainIDs ( diffIDs )
if opts . SharedSize {
root [ n ] = & chainIDs
for _ , id := range chainIDs {
layers [ id ] = layers [ id ] + 1
}
}
2022-07-18 10:51:49 +00:00
size , err := img . Size ( ctx )
if err != nil {
return nil , err
}
2022-07-11 15:42:13 +00:00
virtualSize , err := computeVirtualSize ( chainIDs , snapshotSizeFn )
2022-07-06 07:58:10 +00:00
if err != nil {
return nil , err
}
2022-07-25 10:17:23 +00:00
summaries = append ( summaries , & types . ImageSummary {
2022-07-18 10:51:49 +00:00
ParentID : "" ,
ID : img . Target ( ) . Digest . String ( ) ,
Created : img . Metadata ( ) . CreatedAt . Unix ( ) ,
2022-07-25 10:17:23 +00:00
RepoDigests : [ ] string { img . Name ( ) + "@" + img . Target ( ) . Digest . String ( ) } , // "hello-world@sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38"},
RepoTags : [ ] string { img . Name ( ) } ,
2022-07-18 10:51:49 +00:00
Size : size ,
2022-07-25 10:17:23 +00:00
VirtualSize : virtualSize ,
// -1 indicates that the value has not been set (avoids ambiguity
// between 0 (default) and "not set". We cannot use a pointer (nil)
// for this, as the JSON representation uses "omitempty", which would
// consider both "0" and "nil" to be "empty".
SharedSize : - 1 ,
Containers : - 1 ,
2022-07-18 10:51:49 +00:00
} )
}
2022-07-11 15:42:13 +00:00
if opts . SharedSize {
for n , chainIDs := range root {
sharedSize , err := computeSharedSize ( * chainIDs , layers , snapshotSizeFn )
if err != nil {
return nil , err
}
summaries [ n ] . SharedSize = sharedSize
}
}
2022-07-25 10:17:23 +00:00
return summaries , nil
2022-07-18 10:51:49 +00:00
}
2022-07-06 07:58:10 +00:00
2022-07-06 15:46:39 +00:00
type imageFilterFunc func ( image containerd . Image ) bool
// setupFilters constructs an imageFilterFunc from the given imageFilters.
//
// TODO(thaJeztah): reimplement filters using containerd filters: see https://github.com/moby/moby/issues/43845
func ( i * ImageService ) setupFilters ( ctx context . Context , imageFilters filters . Args ) ( imageFilterFunc , error ) {
var fltrs [ ] imageFilterFunc
err := imageFilters . WalkValues ( "before" , func ( value string ) error {
ref , err := reference . ParseDockerRef ( value )
if err != nil {
return err
}
img , err := i . client . GetImage ( ctx , ref . String ( ) )
if img != nil {
t := img . Metadata ( ) . CreatedAt
fltrs = append ( fltrs , func ( image containerd . Image ) bool {
created := image . Metadata ( ) . CreatedAt
return created . Equal ( t ) || created . After ( t )
} )
}
return err
} )
if err != nil {
return nil , err
}
err = imageFilters . WalkValues ( "since" , func ( value string ) error {
ref , err := reference . ParseDockerRef ( value )
if err != nil {
return err
}
img , err := i . client . GetImage ( ctx , ref . String ( ) )
if img != nil {
t := img . Metadata ( ) . CreatedAt
fltrs = append ( fltrs , func ( image containerd . Image ) bool {
created := image . Metadata ( ) . CreatedAt
return created . Equal ( t ) || created . Before ( t )
} )
}
return err
} )
if err != nil {
return nil , err
}
if imageFilters . Contains ( "label" ) {
fltrs = append ( fltrs , func ( image containerd . Image ) bool {
return imageFilters . MatchKVList ( "label" , image . Labels ( ) )
} )
}
return func ( image containerd . Image ) bool {
for _ , filter := range fltrs {
if ! filter ( image ) {
return false
}
}
return true
} , nil
}
2022-07-11 15:42:13 +00:00
func computeVirtualSize ( chainIDs [ ] digest . Digest , sizeFn func ( d digest . Digest ) ( int64 , error ) ) ( int64 , error ) {
2022-07-06 07:58:10 +00:00
var virtualSize int64
2022-07-11 15:42:13 +00:00
for _ , chainID := range chainIDs {
size , err := sizeFn ( chainID )
2022-07-06 07:58:10 +00:00
if err != nil {
return virtualSize , err
}
2022-07-11 15:42:13 +00:00
virtualSize += size
2022-07-06 07:58:10 +00:00
}
return virtualSize , nil
}
2022-07-11 15:42:13 +00:00
func computeSharedSize ( chainIDs [ ] digest . Digest , layers map [ digest . Digest ] int , sizeFn func ( d digest . Digest ) ( int64 , error ) ) ( int64 , error ) {
var sharedSize int64
for _ , chainID := range chainIDs {
if layers [ chainID ] == 1 {
continue
}
size , err := sizeFn ( chainID )
if err != nil {
return 0 , err
}
sharedSize += size
}
return sharedSize , nil
}