Skip to content

Commit 392d33c

Browse files
stevvooethaJeztah
authored andcommitted
image/save: set a stable timestamp for assets
When saving a docker image with `docker save`, output may have the current timestamp, resulting in slightly changed content each time the `save` command gets run. This patch attemtps to stabilize that effort to clean up some spots where we've missed setting the timestamps. It's not totally clear that setting these timestamps to 0 is the correct behavior but it will fix the hash stability problem on output. Signed-off-by: Stephen Day <[email protected]> Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent e324df3 commit 392d33c

1 file changed

Lines changed: 36 additions & 26 deletions

File tree

image/tarexport/save.go

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ func (s *saveSession) save(ctx context.Context, outStream io.Writer) error {
261261
dgst := digest.FromBytes(data)
262262

263263
mFile := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String(), dgst.Encoded())
264-
if err := os.MkdirAll(filepath.Dir(mFile), 0o755); err != nil {
264+
if err := mkdirAllWithChtimes(filepath.Dir(mFile), 0o755, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
265265
return errors.Wrap(err, "error creating blob directory")
266266
}
267267
if err := system.Chtimes(filepath.Dir(mFile), time.Unix(0, 0), time.Unix(0, 0)); err != nil {
@@ -385,6 +385,9 @@ func (s *saveSession) save(ctx context.Context, outStream io.Writer) error {
385385
if err := os.WriteFile(idxFile, data, 0o644); err != nil {
386386
return errors.Wrap(err, "error writing oci index file")
387387
}
388+
if err := system.Chtimes(idxFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil {
389+
return errors.Wrap(err, "error setting oci index file timestamps")
390+
}
388391

389392
return s.writeTar(ctx, tempDir, outStream)
390393
}
@@ -419,6 +422,11 @@ func (s *saveSession) saveImage(ctx context.Context, id image.ID) (_ map[layer.D
419422
return nil, fmt.Errorf("empty export - not implemented")
420423
}
421424

425+
ts := time.Unix(0, 0)
426+
if img.Created != nil {
427+
ts = *img.Created
428+
}
429+
422430
var parent digest.Digest
423431
var layers []layer.DiffID
424432
var foreignSrcs map[layer.DiffID]distribution.Descriptor
@@ -450,7 +458,7 @@ func (s *saveSession) saveImage(ctx context.Context, id image.ID) (_ map[layer.D
450458
}
451459

452460
v1Img.OS = img.OS
453-
src, err := s.saveConfigAndLayer(ctx, rootFS.ChainID(), v1Img, img.Created)
461+
src, err := s.saveConfigAndLayer(ctx, rootFS.ChainID(), v1Img, &ts)
454462
if err != nil {
455463
return nil, err
456464
}
@@ -469,26 +477,22 @@ func (s *saveSession) saveImage(ctx context.Context, id image.ID) (_ map[layer.D
469477
dgst := digest.FromBytes(data)
470478

471479
blobDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String())
472-
if err := os.MkdirAll(blobDir, 0o755); err != nil {
480+
if err := mkdirAllWithChtimes(blobDir, 0o755, ts, ts); err != nil {
473481
return nil, err
474482
}
475-
if img.Created != nil {
476-
if err := system.Chtimes(blobDir, *img.Created, *img.Created); err != nil {
477-
return nil, err
478-
}
479-
if err := system.Chtimes(filepath.Dir(blobDir), *img.Created, *img.Created); err != nil {
480-
return nil, err
481-
}
483+
if err := system.Chtimes(blobDir, ts, ts); err != nil {
484+
return nil, err
485+
}
486+
if err := system.Chtimes(filepath.Dir(blobDir), ts, ts); err != nil {
487+
return nil, err
482488
}
483489

484490
configFile := filepath.Join(blobDir, dgst.Encoded())
485491
if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil {
486492
return nil, err
487493
}
488-
if img.Created != nil {
489-
if err := system.Chtimes(configFile, *img.Created, *img.Created); err != nil {
490-
return nil, err
491-
}
494+
if err := system.Chtimes(configFile, ts, ts); err != nil {
495+
return nil, err
492496
}
493497

494498
s.images[id].layers = layers
@@ -506,6 +510,11 @@ func (s *saveSession) saveConfigAndLayer(ctx context.Context, id layer.ChainID,
506510
span.SetStatus(outErr)
507511
}()
508512

513+
ts := time.Unix(0, 0)
514+
if createdTime != nil {
515+
ts = *createdTime
516+
}
517+
509518
outDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir)
510519

511520
if _, ok := s.savedConfigs[legacyImg.ID]; !ok {
@@ -542,7 +551,7 @@ func (s *saveSession) saveConfigAndLayer(ctx context.Context, id layer.ChainID,
542551

543552
// We use sequential file access to avoid depleting the standby list on
544553
// Windows. On Linux, this equates to a regular os.Create.
545-
if err := os.MkdirAll(filepath.Dir(layerPath), 0o755); err != nil {
554+
if err := mkdirAllWithChtimes(filepath.Dir(layerPath), 0o755, ts, ts); err != nil {
546555
return distribution.Descriptor{}, errors.Wrap(err, "could not create layer dir parent")
547556
}
548557
tarFile, err := sequential.Create(layerPath)
@@ -576,12 +585,10 @@ func (s *saveSession) saveConfigAndLayer(ctx context.Context, id layer.ChainID,
576585
layerPath = filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded())
577586
}
578587

579-
if createdTime != nil {
580-
for _, fname := range []string{outDir, layerPath} {
581-
// todo: maybe save layer created timestamp?
582-
if err := system.Chtimes(fname, *createdTime, *createdTime); err != nil {
583-
return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp")
584-
}
588+
for _, fname := range []string{outDir, layerPath} {
589+
// todo: maybe save layer created timestamp?
590+
if err := system.Chtimes(fname, ts, ts); err != nil {
591+
return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp")
585592
}
586593
}
587594

@@ -608,20 +615,23 @@ func (s *saveSession) saveConfig(legacyImg image.V1Image, outDir string, created
608615
return err
609616
}
610617

618+
ts := time.Unix(0, 0)
619+
if createdTime != nil {
620+
ts = *createdTime
621+
}
622+
611623
cfgDgst := digest.FromBytes(imageConfig)
612624
configPath := filepath.Join(outDir, cfgDgst.Algorithm().String(), cfgDgst.Encoded())
613-
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
625+
if err := mkdirAllWithChtimes(filepath.Dir(configPath), 0o755, ts, ts); err != nil {
614626
return errors.Wrap(err, "could not create layer dir parent")
615627
}
616628

617629
if err := os.WriteFile(configPath, imageConfig, 0o644); err != nil {
618630
return err
619631
}
620632

621-
if createdTime != nil {
622-
if err := system.Chtimes(configPath, *createdTime, *createdTime); err != nil {
623-
return errors.Wrap(err, "could not set config timestamp")
624-
}
633+
if err := system.Chtimes(configPath, ts, ts); err != nil {
634+
return errors.Wrap(err, "could not set config timestamp")
625635
}
626636

627637
s.savedConfigs[legacyImg.ID] = struct{}{}

0 commit comments

Comments
 (0)