archive: set WithModTimeUpperBound when WithSourceDateEpoch is set
WithModTimeUpperBound sets the upper bound value of the ModTime property of the tar entry structs. WithSourceDateEpoch now implies WithModTimeUpperBound too, in addition to WithWhiteoutTime. For moby/buildkit issue 3296 Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
parent
96a39ad53b
commit
a22f0a4c3e
@ -97,7 +97,9 @@ func WriteDiff(ctx context.Context, w io.Writer, a, b string, opts ...WriteDiffO
|
|||||||
func writeDiffNaive(ctx context.Context, w io.Writer, a, b string, o WriteDiffOptions) error {
|
func writeDiffNaive(ctx context.Context, w io.Writer, a, b string, o WriteDiffOptions) error {
|
||||||
var opts []ChangeWriterOpt
|
var opts []ChangeWriterOpt
|
||||||
if o.SourceDateEpoch != nil {
|
if o.SourceDateEpoch != nil {
|
||||||
opts = append(opts, WithWhiteoutTime(*o.SourceDateEpoch))
|
opts = append(opts,
|
||||||
|
WithModTimeUpperBound(*o.SourceDateEpoch),
|
||||||
|
WithWhiteoutTime(*o.SourceDateEpoch))
|
||||||
}
|
}
|
||||||
cw := NewChangeWriter(w, b, opts...)
|
cw := NewChangeWriter(w, b, opts...)
|
||||||
err := fs.Changes(ctx, a, b, cw.HandleChange)
|
err := fs.Changes(ctx, a, b, cw.HandleChange)
|
||||||
@ -495,6 +497,7 @@ func mkparent(ctx context.Context, path, root string, parents []string) error {
|
|||||||
type ChangeWriter struct {
|
type ChangeWriter struct {
|
||||||
tw *tar.Writer
|
tw *tar.Writer
|
||||||
source string
|
source string
|
||||||
|
modTimeUpperBound *time.Time
|
||||||
whiteoutT time.Time
|
whiteoutT time.Time
|
||||||
inodeSrc map[uint64]string
|
inodeSrc map[uint64]string
|
||||||
inodeRefs map[uint64][]string
|
inodeRefs map[uint64][]string
|
||||||
@ -504,6 +507,13 @@ type ChangeWriter struct {
|
|||||||
// ChangeWriterOpt can be specified in NewChangeWriter.
|
// ChangeWriterOpt can be specified in NewChangeWriter.
|
||||||
type ChangeWriterOpt func(cw *ChangeWriter)
|
type ChangeWriterOpt func(cw *ChangeWriter)
|
||||||
|
|
||||||
|
// WithModTimeUpperBound sets the mod time upper bound.
|
||||||
|
func WithModTimeUpperBound(tm time.Time) ChangeWriterOpt {
|
||||||
|
return func(cw *ChangeWriter) {
|
||||||
|
cw.modTimeUpperBound = &tm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithWhiteoutTime sets the whiteout timestamp.
|
// WithWhiteoutTime sets the whiteout timestamp.
|
||||||
func WithWhiteoutTime(tm time.Time) ChangeWriterOpt {
|
func WithWhiteoutTime(tm time.Time) ChangeWriterOpt {
|
||||||
return func(cw *ChangeWriter) {
|
return func(cw *ChangeWriter) {
|
||||||
@ -579,6 +589,9 @@ func (cw *ChangeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, e
|
|||||||
|
|
||||||
// truncate timestamp for compatibility. without PAX stdlib rounds timestamps instead
|
// truncate timestamp for compatibility. without PAX stdlib rounds timestamps instead
|
||||||
hdr.Format = tar.FormatPAX
|
hdr.Format = tar.FormatPAX
|
||||||
|
if cw.modTimeUpperBound != nil && hdr.ModTime.After(*cw.modTimeUpperBound) {
|
||||||
|
hdr.ModTime = *cw.modTimeUpperBound
|
||||||
|
}
|
||||||
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
||||||
hdr.AccessTime = time.Time{}
|
hdr.AccessTime = time.Time{}
|
||||||
hdr.ChangeTime = time.Time{}
|
hdr.ChangeTime = time.Time{}
|
||||||
|
@ -91,7 +91,10 @@ type WriteDiffOptions struct {
|
|||||||
|
|
||||||
writeDiffFunc func(context.Context, io.Writer, string, string, WriteDiffOptions) error
|
writeDiffFunc func(context.Context, io.Writer, string, string, WriteDiffOptions) error
|
||||||
|
|
||||||
// SourceDateEpoch specifies the timestamp used for whiteouts to provide control for reproducibility.
|
// SourceDateEpoch specifies the following timestamps to provide control for reproducibility.
|
||||||
|
// - The upper bound timestamp of the diff contents
|
||||||
|
// - The timestamp of the whiteouts
|
||||||
|
//
|
||||||
// See also https://reproducible-builds.org/docs/source-date-epoch/ .
|
// See also https://reproducible-builds.org/docs/source-date-epoch/ .
|
||||||
SourceDateEpoch *time.Time
|
SourceDateEpoch *time.Time
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/containerd/containerd/pkg/testutil"
|
"github.com/containerd/containerd/pkg/testutil"
|
||||||
"github.com/containerd/continuity/fs"
|
"github.com/containerd/continuity/fs"
|
||||||
"github.com/containerd/continuity/fs/fstest"
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
exec "golang.org/x/sys/execabs"
|
exec "golang.org/x/sys/execabs"
|
||||||
)
|
)
|
||||||
@ -1157,20 +1158,36 @@ func TestDiffTar(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWhiteoutSourceDateEpoch(t *testing.T) {
|
func TestSourceDateEpoch(t *testing.T) {
|
||||||
sourceDateEpoch, err := time.Parse(time.RFC3339, "2022-01-23T12:34:56Z")
|
sourceDateEpoch, err := time.Parse(time.RFC3339, "2022-01-23T12:34:56Z")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
past, err := time.Parse(time.RFC3339, "2022-01-01T00:00:00Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, past.Before(sourceDateEpoch))
|
||||||
|
veryRecent := time.Now()
|
||||||
|
require.True(t, veryRecent.After(sourceDateEpoch))
|
||||||
|
|
||||||
opts := []WriteDiffOpt{WithSourceDateEpoch(&sourceDateEpoch)}
|
opts := []WriteDiffOpt{WithSourceDateEpoch(&sourceDateEpoch)}
|
||||||
validators := []tarEntryValidator{
|
validators := []tarEntryValidator{
|
||||||
composeValidators(whiteoutEntry("f1"), requireModTime(sourceDateEpoch)),
|
composeValidators(whiteoutEntry("f1"), requireModTime(sourceDateEpoch)),
|
||||||
|
composeValidators(fileEntry("f2", []byte("content2"), 0644), requireModTime(past)),
|
||||||
|
composeValidators(fileEntry("f3", []byte("content3"), 0644), requireModTime(sourceDateEpoch)),
|
||||||
}
|
}
|
||||||
a := fstest.Apply(
|
a := fstest.Apply(
|
||||||
fstest.CreateFile("/f1", []byte("content"), 0644),
|
fstest.CreateFile("/f1", []byte("content"), 0644),
|
||||||
)
|
)
|
||||||
b := fstest.Apply(
|
b := fstest.Apply(
|
||||||
|
// Remove f1; the timestamp of the tar entry will be sourceDateEpoch
|
||||||
fstest.RemoveAll("/f1"),
|
fstest.RemoveAll("/f1"),
|
||||||
|
// Create f2 with the past timestamp; the timestamp of the tar entry will be past (< sourceDateEpoch)
|
||||||
|
fstest.CreateFile("/f2", []byte("content2"), 0644),
|
||||||
|
fstest.Chtimes("/f2", past, past),
|
||||||
|
// Create f3 with the veryRecent timestamp; the timestamp of the tar entry will be sourceDateEpoch
|
||||||
|
fstest.CreateFile("/f3", []byte("content3"), 0644),
|
||||||
|
fstest.Chtimes("/f3", veryRecent, veryRecent),
|
||||||
)
|
)
|
||||||
makeDiffTarTest(validators, a, b, opts...)(t)
|
makeDiffTarTest(validators, a, b, opts...)(t)
|
||||||
|
makeDiffTarReproTest(a, b, opts...)(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
type tarEntryValidator func(*tar.Header, []byte) error
|
type tarEntryValidator func(*tar.Header, []byte) error
|
||||||
@ -1303,6 +1320,54 @@ func makeDiffTarTest(validators []tarEntryValidator, a, b fstest.Applier, opts .
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeDiffTar(t *testing.T, a, b fstest.Applier, opts ...WriteDiffOpt) (digest.Digest, []byte) {
|
||||||
|
ad := t.TempDir()
|
||||||
|
if err := a.Apply(ad); err != nil {
|
||||||
|
t.Fatalf("failed to apply a: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bd := t.TempDir()
|
||||||
|
if err := fs.CopyDir(bd, ad); err != nil {
|
||||||
|
t.Fatalf("failed to copy dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := b.Apply(bd); err != nil {
|
||||||
|
t.Fatalf("failed to apply b: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := Diff(context.Background(), ad, bd, opts...)
|
||||||
|
defer rc.Close()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
r := io.TeeReader(rc, &buf)
|
||||||
|
dgst, err := digest.FromReader(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = rc.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return dgst, buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDiffTarReproTest(a, b fstest.Applier, opts ...WriteDiffOpt) func(*testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
const (
|
||||||
|
count = 5
|
||||||
|
delay = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
var lastDigest digest.Digest
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
dgst, _ := makeDiffTar(t, a, b, opts...)
|
||||||
|
t.Logf("#%d: digest %s", i, dgst)
|
||||||
|
if lastDigest == "" {
|
||||||
|
lastDigest = dgst
|
||||||
|
} else if dgst != lastDigest {
|
||||||
|
t.Fatalf("expected digest %s, got %s", lastDigest, dgst)
|
||||||
|
}
|
||||||
|
time.Sleep(delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type diffApplier struct{}
|
type diffApplier struct{}
|
||||||
|
|
||||||
func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
|
func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user