99 "path/filepath"
1010 "time"
1111
12+ "github.com/containerd/containerd/images"
1213 "github.com/docker/distribution"
1314 "github.com/docker/distribution/reference"
1415 "github.com/docker/docker/image"
@@ -18,6 +19,8 @@ import (
1819 "github.com/docker/docker/pkg/system"
1920 "github.com/moby/sys/sequential"
2021 "github.com/opencontainers/go-digest"
22+ "github.com/opencontainers/image-spec/specs-go"
23+ ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2124 "github.com/pkg/errors"
2225)
2326
@@ -190,14 +193,67 @@ func (s *saveSession) save(outStream io.Writer) error {
190193 var manifest []manifestItem
191194 var parentLinks []parentLink
192195
196+ var manifestDescriptors []ocispec.Descriptor
197+
193198 for id , imageDescr := range s .images {
194199 foreignSrcs , err := s .saveImage (id )
195200 if err != nil {
196201 return err
197202 }
198203
199- var repoTags []string
200- var layers []string
204+ var (
205+ repoTags []string
206+ layers []string
207+ foreign = make ([]ocispec.Descriptor , 0 , len (foreignSrcs ))
208+ )
209+
210+ for _ , desc := range foreignSrcs {
211+ foreign = append (foreign , ocispec.Descriptor {
212+ MediaType : desc .MediaType ,
213+ Digest : desc .Digest ,
214+ Size : desc .Size ,
215+ URLs : desc .URLs ,
216+ Annotations : desc .Annotations ,
217+ Platform : desc .Platform ,
218+ })
219+ }
220+
221+ imgPlat := imageDescr .image .Platform ()
222+
223+ m := ocispec.Manifest {
224+ Versioned : specs.Versioned {
225+ SchemaVersion : 2 ,
226+ },
227+ MediaType : ocispec .MediaTypeImageManifest ,
228+ Config : ocispec.Descriptor {
229+ MediaType : ocispec .MediaTypeImageConfig ,
230+ Digest : digest .Digest (imageDescr .image .ID ()),
231+ Size : int64 (len (imageDescr .image .RawJSON ())),
232+ Platform : & imgPlat ,
233+ },
234+ Layers : foreign ,
235+ }
236+
237+ data , err := json .Marshal (m )
238+ if err != nil {
239+ return errors .Wrap (err , "error marshaling manifest" )
240+ }
241+ dgst := digest .FromBytes (data )
242+
243+ mFile := filepath .Join (s .outDir , "blobs" , dgst .Algorithm ().String (), dgst .Encoded ())
244+ if err := os .MkdirAll (filepath .Dir (mFile ), 0o755 ); err != nil {
245+ return errors .Wrap (err , "error creating blob directory" )
246+ }
247+ if err := system .Chtimes (filepath .Dir (mFile ), time .Unix (0 , 0 ), time .Unix (0 , 0 )); err != nil {
248+ return errors .Wrap (err , "error setting blob directory timestamps" )
249+ }
250+ if err := os .WriteFile (mFile , data , 0o644 ); err != nil {
251+ return errors .Wrap (err , "error writing oci manifest file" )
252+ }
253+ if err := system .Chtimes (mFile , time .Unix (0 , 0 ), time .Unix (0 , 0 )); err != nil {
254+ return errors .Wrap (err , "error setting blob directory timestamps" )
255+ }
256+ size := int64 (len (data ))
201257
202258 for _ , ref := range imageDescr .refs {
203259 familiarName := reference .FamiliarName (ref )
@@ -206,6 +262,17 @@ func (s *saveSession) save(outStream io.Writer) error {
206262 }
207263 reposLegacy [familiarName ][ref .Tag ()] = imageDescr .layers [len (imageDescr .layers )- 1 ].Encoded ()
208264 repoTags = append (repoTags , reference .FamiliarString (ref ))
265+
266+ manifestDescriptors = append (manifestDescriptors , ocispec.Descriptor {
267+ MediaType : ocispec .MediaTypeImageManifest ,
268+ Digest : dgst ,
269+ Size : size ,
270+ Platform : m .Config .Platform ,
271+ Annotations : map [string ]string {
272+ images .AnnotationImageName : ref .String (),
273+ ocispec .AnnotationRefName : ref .Tag (),
274+ },
275+ })
209276 }
210277
211278 for _ , l := range imageDescr .layers {
@@ -251,8 +318,8 @@ func (s *saveSession) save(outStream io.Writer) error {
251318 }
252319 }
253320
254- manifestFileName := filepath .Join (tempDir , manifestFileName )
255- f , err := os .OpenFile (manifestFileName , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0644 )
321+ manifestPath := filepath .Join (tempDir , manifestFileName )
322+ f , err := os .OpenFile (manifestPath , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0644 )
256323 if err != nil {
257324 return err
258325 }
@@ -264,10 +331,34 @@ func (s *saveSession) save(outStream io.Writer) error {
264331
265332 f .Close ()
266333
267- if err := system .Chtimes (manifestFileName , time .Unix (0 , 0 ), time .Unix (0 , 0 )); err != nil {
334+ if err := system .Chtimes (manifestPath , time .Unix (0 , 0 ), time .Unix (0 , 0 )); err != nil {
268335 return err
269336 }
270337
338+ layoutPath := filepath .Join (tempDir , ociLayoutFilename )
339+ if err := os .WriteFile (layoutPath , []byte (ociLayoutContent ), 0o644 ); err != nil {
340+ return errors .Wrap (err , "error writing oci layout file" )
341+ }
342+ if err := system .Chtimes (layoutPath , time .Unix (0 , 0 ), time .Unix (0 , 0 )); err != nil {
343+ return errors .Wrap (err , "error setting oci layout file timestamps" )
344+ }
345+
346+ data , err := json .Marshal (ocispec.Index {
347+ Versioned : specs.Versioned {
348+ SchemaVersion : 2 ,
349+ },
350+ MediaType : ocispec .MediaTypeImageIndex ,
351+ Manifests : manifestDescriptors ,
352+ })
353+ if err != nil {
354+ return errors .Wrap (err , "error marshaling oci index" )
355+ }
356+
357+ idxFile := filepath .Join (s .outDir , ociIndexFileName )
358+ if err := os .WriteFile (idxFile , data , 0o644 ); err != nil {
359+ return errors .Wrap (err , "error writing oci index file" )
360+ }
361+
271362 fs , err := archive .Tar (tempDir , archive .Uncompressed )
272363 if err != nil {
273364 return err
@@ -365,7 +456,7 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
365456 cfgDgst := digest .FromBytes (imageConfig )
366457 configPath := filepath .Join (outDir , cfgDgst .Algorithm ().String (), cfgDgst .Encoded ())
367458 if err := os .MkdirAll (filepath .Dir (configPath ), 0755 ); err != nil {
368- return distribution.Descriptor {}, fmt . Errorf ( "could not create layer dir parent: %w" , err )
459+ return distribution.Descriptor {}, errors . Wrap ( err , "could not create layer dir parent" )
369460 }
370461
371462 if err := os .WriteFile (configPath , imageConfig , 0644 ); err != nil {
@@ -390,11 +481,11 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
390481 // We use sequential file access to avoid depleting the standby list on
391482 // Windows. On Linux, this equates to a regular os.Create.
392483 if err := os .MkdirAll (filepath .Dir (layerPath ), 0755 ); err != nil {
393- return distribution.Descriptor {}, fmt . Errorf ( "could not create layer dir parent: %w" , err )
484+ return distribution.Descriptor {}, errors . Wrap ( err , "could not create layer dir parent" )
394485 }
395486 tarFile , err := sequential .Create (layerPath )
396487 if err != nil {
397- return distribution.Descriptor {}, fmt . Errorf ( "error creating layer file: %w" , err )
488+ return distribution.Descriptor {}, errors . Wrap ( err , "error creating layer file" )
398489 }
399490 defer tarFile .Close ()
400491
@@ -411,7 +502,7 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
411502 for _ , fname := range []string {outDir , configPath , layerPath } {
412503 // todo: maybe save layer created timestamp?
413504 if err := system .Chtimes (fname , createdTime , createdTime ); err != nil {
414- return distribution.Descriptor {}, err
505+ return distribution.Descriptor {}, errors . Wrap ( err , "could not set layer timestamp" )
415506 }
416507 }
417508
0 commit comments