Merge pull request #7710 from AkihiroSuda/source-date-epoch-with-mod-time-upper-bound
archive: set WithModTimeUpperBound when WithSourceDateEpoch is set
This commit is contained in:
commit
9c9f564a35
@ -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)
|
||||||
@ -493,17 +495,25 @@ func mkparent(ctx context.Context, path, root string, parents []string) error {
|
|||||||
// See also https://github.com/opencontainers/image-spec/blob/main/layer.md for details
|
// See also https://github.com/opencontainers/image-spec/blob/main/layer.md for details
|
||||||
// about OCI layers
|
// about OCI layers
|
||||||
type ChangeWriter struct {
|
type ChangeWriter struct {
|
||||||
tw *tar.Writer
|
tw *tar.Writer
|
||||||
source string
|
source string
|
||||||
whiteoutT time.Time
|
modTimeUpperBound *time.Time
|
||||||
inodeSrc map[uint64]string
|
whiteoutT time.Time
|
||||||
inodeRefs map[uint64][]string
|
inodeSrc map[uint64]string
|
||||||
addedDirs map[string]struct{}
|
inodeRefs map[uint64][]string
|
||||||
|
addedDirs map[string]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