1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package daemon
16
17 import (
18 "bytes"
19 "context"
20 "io"
21 "sync"
22 "time"
23
24 api "github.com/docker/docker/api/types"
25 "github.com/docker/docker/api/types/container"
26
27 "github.com/google/go-containerregistry/pkg/name"
28 v1 "github.com/google/go-containerregistry/pkg/v1"
29 "github.com/google/go-containerregistry/pkg/v1/tarball"
30 "github.com/google/go-containerregistry/pkg/v1/types"
31 )
32
33 type image struct {
34 ref name.Reference
35 opener *imageOpener
36 tarballImage v1.Image
37 computed bool
38 id *v1.Hash
39 configFile *v1.ConfigFile
40
41 once sync.Once
42 err error
43 }
44
45 type imageOpener struct {
46 ref name.Reference
47 ctx context.Context
48
49 buffered bool
50 client Client
51
52 once sync.Once
53 bytes []byte
54 err error
55 }
56
57 func (i *imageOpener) saveImage() (io.ReadCloser, error) {
58 return i.client.ImageSave(i.ctx, []string{i.ref.Name()})
59 }
60
61 func (i *imageOpener) bufferedOpener() (io.ReadCloser, error) {
62
63 i.once.Do(func() {
64 i.bytes, i.err = func() ([]byte, error) {
65 rc, err := i.saveImage()
66 if err != nil {
67 return nil, err
68 }
69 defer rc.Close()
70
71 return io.ReadAll(rc)
72 }()
73 })
74
75
76 return io.NopCloser(bytes.NewReader(i.bytes)), i.err
77 }
78
79 func (i *imageOpener) opener() tarball.Opener {
80 if i.buffered {
81 return i.bufferedOpener
82 }
83
84
85 return i.saveImage
86 }
87
88
89
90
91 func Image(ref name.Reference, options ...Option) (v1.Image, error) {
92 o, err := makeOptions(options...)
93 if err != nil {
94 return nil, err
95 }
96
97 i := &imageOpener{
98 ref: ref,
99 buffered: o.buffered,
100 client: o.client,
101 ctx: o.ctx,
102 }
103
104 img := &image{
105 ref: ref,
106 opener: i,
107 }
108
109
110
111 id, err := img.ConfigName()
112 if err != nil {
113 return nil, err
114 }
115 img.id = &id
116
117 return img, nil
118 }
119
120 func (i *image) initialize() error {
121
122 if i.tarballImage == nil {
123 i.once.Do(func() {
124 i.tarballImage, i.err = tarball.Image(i.opener.opener(), nil)
125 })
126 }
127 return i.err
128 }
129
130 func (i *image) compute() error {
131
132 if i.computed {
133 return nil
134 }
135
136 inspect, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
137 if err != nil {
138 return err
139 }
140
141 configFile, err := i.computeConfigFile(inspect)
142 if err != nil {
143 return err
144 }
145
146 i.configFile = configFile
147 i.computed = true
148
149 return nil
150 }
151
152 func (i *image) Layers() ([]v1.Layer, error) {
153 if err := i.initialize(); err != nil {
154 return nil, err
155 }
156 return i.tarballImage.Layers()
157 }
158
159 func (i *image) MediaType() (types.MediaType, error) {
160 if err := i.initialize(); err != nil {
161 return "", err
162 }
163 return i.tarballImage.MediaType()
164 }
165
166 func (i *image) Size() (int64, error) {
167 if err := i.initialize(); err != nil {
168 return 0, err
169 }
170 return i.tarballImage.Size()
171 }
172
173 func (i *image) ConfigName() (v1.Hash, error) {
174 if i.id != nil {
175 return *i.id, nil
176 }
177 res, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
178 if err != nil {
179 return v1.Hash{}, err
180 }
181 return v1.NewHash(res.ID)
182 }
183
184 func (i *image) ConfigFile() (*v1.ConfigFile, error) {
185 if err := i.compute(); err != nil {
186 return nil, err
187 }
188 return i.configFile.DeepCopy(), nil
189 }
190
191 func (i *image) RawConfigFile() ([]byte, error) {
192 if err := i.initialize(); err != nil {
193 return nil, err
194 }
195
196
197
198 return i.tarballImage.RawConfigFile()
199 }
200
201 func (i *image) Digest() (v1.Hash, error) {
202 if err := i.initialize(); err != nil {
203 return v1.Hash{}, err
204 }
205 return i.tarballImage.Digest()
206 }
207
208 func (i *image) Manifest() (*v1.Manifest, error) {
209 if err := i.initialize(); err != nil {
210 return nil, err
211 }
212 return i.tarballImage.Manifest()
213 }
214
215 func (i *image) RawManifest() ([]byte, error) {
216 if err := i.initialize(); err != nil {
217 return nil, err
218 }
219 return i.tarballImage.RawManifest()
220 }
221
222 func (i *image) LayerByDigest(h v1.Hash) (v1.Layer, error) {
223 if err := i.initialize(); err != nil {
224 return nil, err
225 }
226 return i.tarballImage.LayerByDigest(h)
227 }
228
229 func (i *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
230 if err := i.initialize(); err != nil {
231 return nil, err
232 }
233 return i.tarballImage.LayerByDiffID(h)
234 }
235
236 func (i *image) configHistory(author string) ([]v1.History, error) {
237 historyItems, err := i.opener.client.ImageHistory(i.opener.ctx, i.ref.String())
238 if err != nil {
239 return nil, err
240 }
241
242 history := make([]v1.History, len(historyItems))
243 for j, h := range historyItems {
244 history[j] = v1.History{
245 Author: author,
246 Created: v1.Time{
247 Time: time.Unix(h.Created, 0).UTC(),
248 },
249 CreatedBy: h.CreatedBy,
250 Comment: h.Comment,
251 EmptyLayer: h.Size == 0,
252 }
253 }
254 return history, nil
255 }
256
257 func (i *image) diffIDs(rootFS api.RootFS) ([]v1.Hash, error) {
258 diffIDs := make([]v1.Hash, len(rootFS.Layers))
259 for j, l := range rootFS.Layers {
260 h, err := v1.NewHash(l)
261 if err != nil {
262 return nil, err
263 }
264 diffIDs[j] = h
265 }
266 return diffIDs, nil
267 }
268
269 func (i *image) computeConfigFile(inspect api.ImageInspect) (*v1.ConfigFile, error) {
270 diffIDs, err := i.diffIDs(inspect.RootFS)
271 if err != nil {
272 return nil, err
273 }
274
275 history, err := i.configHistory(inspect.Author)
276 if err != nil {
277 return nil, err
278 }
279
280 created, err := time.Parse(time.RFC3339Nano, inspect.Created)
281 if err != nil {
282 return nil, err
283 }
284
285 return &v1.ConfigFile{
286 Architecture: inspect.Architecture,
287 Author: inspect.Author,
288 Container: inspect.Container,
289 Created: v1.Time{Time: created},
290 DockerVersion: inspect.DockerVersion,
291 History: history,
292 OS: inspect.Os,
293 RootFS: v1.RootFS{
294 Type: inspect.RootFS.Type,
295 DiffIDs: diffIDs,
296 },
297 Config: i.computeImageConfig(inspect.Config),
298 OSVersion: inspect.OsVersion,
299 }, nil
300 }
301
302 func (i *image) computeImageConfig(config *container.Config) v1.Config {
303 if config == nil {
304 return v1.Config{}
305 }
306
307 c := v1.Config{
308 AttachStderr: config.AttachStderr,
309 AttachStdin: config.AttachStdin,
310 AttachStdout: config.AttachStdout,
311 Cmd: config.Cmd,
312 Domainname: config.Domainname,
313 Entrypoint: config.Entrypoint,
314 Env: config.Env,
315 Hostname: config.Hostname,
316 Image: config.Image,
317 Labels: config.Labels,
318 OnBuild: config.OnBuild,
319 OpenStdin: config.OpenStdin,
320 StdinOnce: config.StdinOnce,
321 Tty: config.Tty,
322 User: config.User,
323 Volumes: config.Volumes,
324 WorkingDir: config.WorkingDir,
325 ArgsEscaped: config.ArgsEscaped,
326 NetworkDisabled: config.NetworkDisabled,
327 MacAddress: config.MacAddress,
328 StopSignal: config.StopSignal,
329 Shell: config.Shell,
330 }
331
332 if config.Healthcheck != nil {
333 c.Healthcheck = &v1.HealthConfig{
334 Test: config.Healthcheck.Test,
335 Interval: config.Healthcheck.Interval,
336 Timeout: config.Healthcheck.Timeout,
337 StartPeriod: config.Healthcheck.StartPeriod,
338 Retries: config.Healthcheck.Retries,
339 }
340 }
341
342 if len(config.ExposedPorts) > 0 {
343 c.ExposedPorts = map[string]struct{}{}
344 for port := range c.ExposedPorts {
345 c.ExposedPorts[port] = struct{}{}
346 }
347 }
348
349 return c
350 }
351
View as plain text