@@ -2,14 +2,219 @@ package containerd
22
33import (
44 "context"
5- "errors"
5+ "encoding/json"
6+ "fmt"
7+ "regexp"
8+ "strconv"
9+ "sync/atomic"
10+ "time"
611
12+ "github.com/containerd/containerd/content"
13+ cerrdefs "github.com/containerd/containerd/errdefs"
14+ containerdimages "github.com/containerd/containerd/images"
15+ cplatforms "github.com/containerd/containerd/platforms"
16+ "github.com/docker/distribution/reference"
17+ containertypes "github.com/docker/docker/api/types/container"
718 imagetype "github.com/docker/docker/api/types/image"
19+ "github.com/docker/docker/daemon/images"
820 "github.com/docker/docker/errdefs"
921 "github.com/docker/docker/image"
22+ "github.com/docker/docker/layer"
23+ "github.com/docker/docker/pkg/platforms"
24+ "github.com/docker/go-connections/nat"
25+ "github.com/opencontainers/go-digest"
26+ ocispec "github.com/opencontainers/image-spec/specs-go/v1"
27+ "github.com/pkg/errors"
28+ "golang.org/x/sync/semaphore"
1029)
1130
31+ var truncatedID = regexp .MustCompile (`^([a-f0-9]{4,64})$` )
32+
1233// GetImage returns an image corresponding to the image referred to by refOrID.
13- func (i * ImageService ) GetImage (ctx context.Context , refOrID string , options imagetype.GetImageOpts ) (retImg * image.Image , retErr error ) {
14- return nil , errdefs .NotImplemented (errors .New ("not implemented" ))
34+ func (i * ImageService ) GetImage (ctx context.Context , refOrID string , options imagetype.GetImageOpts ) (* image.Image , error ) {
35+ desc , err := i .resolveDescriptor (ctx , refOrID )
36+ if err != nil {
37+ return nil , err
38+ }
39+
40+ platform := platforms .AllPlatformsWithPreference (cplatforms .Default ())
41+ if options .Platform != nil {
42+ platform = cplatforms .OnlyStrict (* options .Platform )
43+ }
44+
45+ cs := i .client .ContentStore ()
46+ conf , err := containerdimages .Config (ctx , cs , desc , platform )
47+ if err != nil {
48+ return nil , err
49+ }
50+
51+ imageConfigBytes , err := content .ReadBlob (ctx , cs , conf )
52+ if err != nil {
53+ return nil , err
54+ }
55+
56+ var ociimage ocispec.Image
57+ if err := json .Unmarshal (imageConfigBytes , & ociimage ); err != nil {
58+ return nil , err
59+ }
60+
61+ rootfs := image .NewRootFS ()
62+ for _ , id := range ociimage .RootFS .DiffIDs {
63+ rootfs .Append (layer .DiffID (id ))
64+ }
65+ exposedPorts := make (nat.PortSet , len (ociimage .Config .ExposedPorts ))
66+ for k , v := range ociimage .Config .ExposedPorts {
67+ exposedPorts [nat .Port (k )] = v
68+ }
69+
70+ img := image .NewImage (image .ID (desc .Digest ))
71+ img .V1Image = image.V1Image {
72+ ID : string (desc .Digest ),
73+ OS : ociimage .OS ,
74+ Architecture : ociimage .Architecture ,
75+ Config : & containertypes.Config {
76+ Entrypoint : ociimage .Config .Entrypoint ,
77+ Env : ociimage .Config .Env ,
78+ Cmd : ociimage .Config .Cmd ,
79+ User : ociimage .Config .User ,
80+ WorkingDir : ociimage .Config .WorkingDir ,
81+ ExposedPorts : exposedPorts ,
82+ Volumes : ociimage .Config .Volumes ,
83+ Labels : ociimage .Config .Labels ,
84+ StopSignal : ociimage .Config .StopSignal ,
85+ },
86+ }
87+
88+ img .RootFS = rootfs
89+
90+ if options .Details {
91+ lastUpdated := time .Unix (0 , 0 )
92+ size , err := i .size (ctx , desc , platform )
93+ if err != nil {
94+ return nil , err
95+ }
96+
97+ tagged , err := i .client .ImageService ().List (ctx , "target.digest==" + desc .Digest .String ())
98+ if err != nil {
99+ return nil , err
100+ }
101+ tags := make ([]reference.Named , 0 , len (tagged ))
102+ for _ , i := range tagged {
103+ if i .UpdatedAt .After (lastUpdated ) {
104+ lastUpdated = i .UpdatedAt
105+ }
106+ name , err := reference .ParseNamed (i .Name )
107+ if err != nil {
108+ return nil , err
109+ }
110+ tags = append (tags , name )
111+ }
112+
113+ img .Details = & image.Details {
114+ References : tags ,
115+ Size : size ,
116+ Metadata : nil ,
117+ Driver : i .snapshotter ,
118+ LastUpdated : lastUpdated ,
119+ }
120+ }
121+
122+ return img , nil
123+ }
124+
125+ // size returns the total size of the image's packed resources.
126+ func (i * ImageService ) size (ctx context.Context , desc ocispec.Descriptor , platform cplatforms.MatchComparer ) (int64 , error ) {
127+ var size int64
128+
129+ cs := i .client .ContentStore ()
130+ handler := containerdimages .LimitManifests (containerdimages .ChildrenHandler (cs ), platform , 1 )
131+
132+ var wh containerdimages.HandlerFunc = func (ctx context.Context , desc ocispec.Descriptor ) ([]ocispec.Descriptor , error ) {
133+ children , err := handler (ctx , desc )
134+ if err != nil {
135+ if ! cerrdefs .IsNotFound (err ) {
136+ return nil , err
137+ }
138+ }
139+
140+ atomic .AddInt64 (& size , desc .Size )
141+
142+ return children , nil
143+ }
144+
145+ l := semaphore .NewWeighted (3 )
146+ if err := containerdimages .Dispatch (ctx , wh , l , desc ); err != nil {
147+ return 0 , err
148+ }
149+
150+ return size , nil
151+ }
152+
153+ // resolveDescriptor searches for a descriptor based on the given
154+ // reference or identifier. Returns the descriptor of
155+ // the image, which could be a manifest list, manifest, or config.
156+ func (i * ImageService ) resolveDescriptor (ctx context.Context , refOrID string ) (ocispec.Descriptor , error ) {
157+ parsed , err := reference .ParseAnyReference (refOrID )
158+ if err != nil {
159+ return ocispec.Descriptor {}, errdefs .InvalidParameter (err )
160+ }
161+
162+ is := i .client .ImageService ()
163+
164+ digested , ok := parsed .(reference.Digested )
165+ if ok {
166+ imgs , err := is .List (ctx , "target.digest==" + digested .Digest ().String ())
167+ if err != nil {
168+ return ocispec.Descriptor {}, errors .Wrap (err , "failed to lookup digest" )
169+ }
170+ if len (imgs ) == 0 {
171+ return ocispec.Descriptor {}, images.ErrImageDoesNotExist {Ref : parsed }
172+ }
173+
174+ return imgs [0 ].Target , nil
175+ }
176+
177+ ref := reference .TagNameOnly (parsed .(reference.Named )).String ()
178+
179+ // If the identifier could be a short ID, attempt to match
180+ if truncatedID .MatchString (refOrID ) {
181+ filters := []string {
182+ fmt .Sprintf ("name==%q" , ref ), // Or it could just look like one.
183+ "target.digest~=" + strconv .Quote (fmt .Sprintf (`sha256:^%s[0-9a-fA-F]{%d}$` , regexp .QuoteMeta (refOrID ), 64 - len (refOrID ))),
184+ }
185+ imgs , err := is .List (ctx , filters ... )
186+ if err != nil {
187+ return ocispec.Descriptor {}, err
188+ }
189+
190+ if len (imgs ) == 0 {
191+ return ocispec.Descriptor {}, images.ErrImageDoesNotExist {Ref : parsed }
192+ }
193+ if len (imgs ) > 1 {
194+ digests := map [digest.Digest ]struct {}{}
195+ for _ , img := range imgs {
196+ if img .Name == ref {
197+ return img .Target , nil
198+ }
199+ digests [img .Target .Digest ] = struct {}{}
200+ }
201+
202+ if len (digests ) > 1 {
203+ return ocispec.Descriptor {}, errdefs .NotFound (errors .New ("ambiguous reference" ))
204+ }
205+ }
206+
207+ return imgs [0 ].Target , nil
208+ }
209+
210+ img , err := is .Get (ctx , ref )
211+ if err != nil {
212+ // TODO(containerd): error translation can use common function
213+ if ! cerrdefs .IsNotFound (err ) {
214+ return ocispec.Descriptor {}, err
215+ }
216+ return ocispec.Descriptor {}, images.ErrImageDoesNotExist {Ref : parsed }
217+ }
218+
219+ return img .Target , nil
15220}
0 commit comments