@@ -19,6 +19,10 @@ import (
1919 "context"
2020 "io"
2121 "sync"
22+ "time"
23+
24+ api "github.com/docker/docker/api/types"
25+ "github.com/docker/docker/api/types/container"
2226
2327 "github.com/google/go-containerregistry/pkg/name"
2428 v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -30,7 +34,9 @@ type image struct {
3034 ref name.Reference
3135 opener * imageOpener
3236 tarballImage v1.Image
37+ computed bool
3338 id * v1.Hash
39+ configFile * v1.ConfigFile
3440
3541 once sync.Once
3642 err error
@@ -121,6 +127,28 @@ func (i *image) initialize() error {
121127 return i .err
122128}
123129
130+ func (i * image ) compute () error {
131+ // Don't re-compute if already computed.
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+
124152func (i * image ) Layers () ([]v1.Layer , error ) {
125153 if err := i .initialize (); err != nil {
126154 return nil , err
@@ -154,16 +182,19 @@ func (i *image) ConfigName() (v1.Hash, error) {
154182}
155183
156184func (i * image ) ConfigFile () (* v1.ConfigFile , error ) {
157- if err := i .initialize (); err != nil {
185+ if err := i .compute (); err != nil {
158186 return nil , err
159187 }
160- return i .tarballImage . ConfigFile ()
188+ return i .configFile . DeepCopy (), nil
161189}
162190
163191func (i * image ) RawConfigFile () ([]byte , error ) {
164192 if err := i .initialize (); err != nil {
165193 return nil , err
166194 }
195+
196+ // RawConfigFile cannot be generated from "docker inspect" because Docker Engine API returns serialized data,
197+ // and formatting information of the raw config such as indent and prefix will be lost.
167198 return i .tarballImage .RawConfigFile ()
168199}
169200
@@ -201,3 +232,119 @@ func (i *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
201232 }
202233 return i .tarballImage .LayerByDiffID (h )
203234}
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+ }
0 commit comments