fix(epoch): override all timestamps unconditionally and route warnings through m.Out

ApplySourceDateEpoch previously only stamped entries with a zero ModTime,
but charts loaded from disk (via loader.LoadDir) already have non-zero
ModTimes populated from the filesystem. This made the function a no-op
in the common helm-package path, defeating reproducible builds.

The function now unconditionally overrides every ModTime to the
SOURCE_DATE_EPOCH value, which is the correct behaviour for producing
bit-for-bit identical archives regardless of filesystem metadata.

Additionally, tarFromLocalDir in pkg/downloader/manager.go was writing
its SOURCE_DATE_EPOCH warning directly to os.Stderr, which is
inconsistent with the rest of Manager that routes user-facing output
through m.Out. The function now accepts an io.Writer parameter and the
call site passes m.Out.

Tests updated to verify that existing (non-zero) timestamps are
overridden rather than preserved.

Signed-off-by: Maxime Kawawa-Beaudan <maxkawab@gmail.com>
Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
pull/31845/head
Maxime Grenu 7 months ago
parent f2d339dfa4
commit 81c8a2d844
No known key found for this signature in database
GPG Key ID: 532A7B7866CFDC51

@ -47,37 +47,30 @@ func ParseSourceDateEpoch() (time.Time, error) {
return time.Unix(epoch, 0), nil return time.Unix(epoch, 0), nil
} }
// ApplySourceDateEpoch sets the ModTime on the chart and all of its entries // ApplySourceDateEpoch overrides the ModTime on the chart and all of its
// that currently have a zero ModTime to t. It recurses into dependencies. // entries to t, ensuring reproducible archives regardless of the original
// timestamps. It recurses into dependencies.
// When t is the zero time this is a no-op. // When t is the zero time this is a no-op.
func ApplySourceDateEpoch(c *chart.Chart, t time.Time) { func ApplySourceDateEpoch(c *chart.Chart, t time.Time) {
if t.IsZero() { if t.IsZero() {
return return
} }
if c.ModTime.IsZero() {
c.ModTime = t c.ModTime = t
} if c.Lock != nil {
if c.Lock != nil && c.Lock.Generated.IsZero() {
c.Lock.Generated = t c.Lock.Generated = t
} }
if c.Schema != nil && c.SchemaModTime.IsZero() { if c.Schema != nil {
c.SchemaModTime = t c.SchemaModTime = t
} }
for _, f := range c.Raw { for _, f := range c.Raw {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, f := range c.Templates { for _, f := range c.Templates {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, f := range c.Files { for _, f := range c.Files {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, dep := range c.Dependencies() { for _, dep := range c.Dependencies() {
ApplySourceDateEpoch(dep, t) ApplySourceDateEpoch(dep, t)
} }

@ -123,7 +123,7 @@ func TestApplySourceDateEpoch(t *testing.T) {
} }
} }
func TestApplySourceDateEpochPreservesExisting(t *testing.T) { func TestApplySourceDateEpochOverridesExisting(t *testing.T) {
epoch := time.Unix(1700000000, 0) epoch := time.Unix(1700000000, 0)
existing := time.Unix(1600000000, 0) existing := time.Unix(1600000000, 0)
@ -133,12 +133,28 @@ func TestApplySourceDateEpochPreservesExisting(t *testing.T) {
Version: "0.1.0", Version: "0.1.0",
}, },
ModTime: existing, ModTime: existing,
Templates: []*common.File{
{Name: "templates/test.yaml", ModTime: existing},
},
Files: []*common.File{
{Name: "README.md", ModTime: existing},
},
} }
ApplySourceDateEpoch(c, epoch) ApplySourceDateEpoch(c, epoch)
if !c.ModTime.Equal(existing) { if !c.ModTime.Equal(epoch) {
t.Errorf("Chart.ModTime = %v, want existing %v", c.ModTime, existing) t.Errorf("Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
}
for _, f := range c.Templates {
if !f.ModTime.Equal(epoch) {
t.Errorf("Template %s ModTime = %v, want epoch %v", f.Name, f.ModTime, epoch)
}
}
for _, f := range c.Files {
if !f.ModTime.Equal(epoch) {
t.Errorf("File %s ModTime = %v, want epoch %v", f.Name, f.ModTime, epoch)
}
} }
} }
@ -185,9 +201,9 @@ func TestApplySourceDateEpochDependencies(t *testing.T) {
ApplySourceDateEpoch(c, epoch) ApplySourceDateEpoch(c, epoch)
// Parent chart already had a ModTime, so it should be preserved. // Parent chart had an existing ModTime, but it should be overridden.
if !c.ModTime.Equal(existing) { if !c.ModTime.Equal(epoch) {
t.Errorf("parent Chart.ModTime = %v, want existing %v", c.ModTime, existing) t.Errorf("parent Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
} }
// Dependency had a zero ModTime, so it should be stamped. // Dependency had a zero ModTime, so it should be stamped.
if !dep.ModTime.Equal(epoch) { if !dep.ModTime.Equal(epoch) {

@ -47,37 +47,30 @@ func ParseSourceDateEpoch() (time.Time, error) {
return time.Unix(epoch, 0), nil return time.Unix(epoch, 0), nil
} }
// ApplySourceDateEpoch sets the ModTime on the chart and all of its entries // ApplySourceDateEpoch overrides the ModTime on the chart and all of its
// that currently have a zero ModTime to t. It recurses into dependencies. // entries to t, ensuring reproducible archives regardless of the original
// timestamps. It recurses into dependencies.
// When t is the zero time this is a no-op. // When t is the zero time this is a no-op.
func ApplySourceDateEpoch(c *chart.Chart, t time.Time) { func ApplySourceDateEpoch(c *chart.Chart, t time.Time) {
if t.IsZero() { if t.IsZero() {
return return
} }
if c.ModTime.IsZero() {
c.ModTime = t c.ModTime = t
} if c.Lock != nil {
if c.Lock != nil && c.Lock.Generated.IsZero() {
c.Lock.Generated = t c.Lock.Generated = t
} }
if c.Schema != nil && c.SchemaModTime.IsZero() { if c.Schema != nil {
c.SchemaModTime = t c.SchemaModTime = t
} }
for _, f := range c.Raw { for _, f := range c.Raw {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, f := range c.Templates { for _, f := range c.Templates {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, f := range c.Files { for _, f := range c.Files {
if f.ModTime.IsZero() {
f.ModTime = t f.ModTime = t
} }
}
for _, dep := range c.Dependencies() { for _, dep := range c.Dependencies() {
ApplySourceDateEpoch(dep, t) ApplySourceDateEpoch(dep, t)
} }

@ -123,7 +123,7 @@ func TestApplySourceDateEpoch(t *testing.T) {
} }
} }
func TestApplySourceDateEpochPreservesExisting(t *testing.T) { func TestApplySourceDateEpochOverridesExisting(t *testing.T) {
epoch := time.Unix(1700000000, 0) epoch := time.Unix(1700000000, 0)
existing := time.Unix(1600000000, 0) existing := time.Unix(1600000000, 0)
@ -133,12 +133,28 @@ func TestApplySourceDateEpochPreservesExisting(t *testing.T) {
Version: "0.1.0", Version: "0.1.0",
}, },
ModTime: existing, ModTime: existing,
Templates: []*common.File{
{Name: "templates/test.yaml", ModTime: existing},
},
Files: []*common.File{
{Name: "README.md", ModTime: existing},
},
} }
ApplySourceDateEpoch(c, epoch) ApplySourceDateEpoch(c, epoch)
if !c.ModTime.Equal(existing) { if !c.ModTime.Equal(epoch) {
t.Errorf("Chart.ModTime = %v, want existing %v", c.ModTime, existing) t.Errorf("Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
}
for _, f := range c.Templates {
if !f.ModTime.Equal(epoch) {
t.Errorf("Template %s ModTime = %v, want epoch %v", f.Name, f.ModTime, epoch)
}
}
for _, f := range c.Files {
if !f.ModTime.Equal(epoch) {
t.Errorf("File %s ModTime = %v, want epoch %v", f.Name, f.ModTime, epoch)
}
} }
} }
@ -187,9 +203,9 @@ func TestApplySourceDateEpochDependencies(t *testing.T) {
ApplySourceDateEpoch(c, epoch) ApplySourceDateEpoch(c, epoch)
// Parent chart already had a ModTime, so it should be preserved. // Parent chart had an existing ModTime, but it should be overridden.
if !c.ModTime.Equal(existing) { if !c.ModTime.Equal(epoch) {
t.Errorf("parent Chart.ModTime = %v, want existing %v", c.ModTime, existing) t.Errorf("parent Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
} }
// Dependency had a zero ModTime, so it should be stamped. // Dependency had a zero ModTime, so it should be stamped.
if !dep.ModTime.Equal(epoch) { if !dep.ModTime.Equal(epoch) {

@ -304,7 +304,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
if m.Debug { if m.Debug {
fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository) fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository)
} }
ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath) ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath, m.Out)
if err != nil { if err != nil {
saveError = err saveError = err
break break
@ -873,7 +873,7 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error {
} }
// archive a dep chart from local directory and save it into destPath // archive a dep chart from local directory and save it into destPath
func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) { func tarFromLocalDir(chartpath, name, repo, version, destPath string, out io.Writer) (string, error) {
if !strings.HasPrefix(repo, "file://") { if !strings.HasPrefix(repo, "file://") {
return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo)
} }
@ -902,7 +902,7 @@ func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, e
// Apply SOURCE_DATE_EPOCH for reproducible builds if set. // Apply SOURCE_DATE_EPOCH for reproducible builds if set.
epoch, epochErr := chartutil.ParseSourceDateEpoch() epoch, epochErr := chartutil.ParseSourceDateEpoch()
if epochErr != nil { if epochErr != nil {
fmt.Fprintf(os.Stderr, "WARNING: %v\n", epochErr) fmt.Fprintf(out, "WARNING: %v\n", epochErr)
} }
chartutil.ApplySourceDateEpoch(ch, epoch) chartutil.ApplySourceDateEpoch(ch, epoch)

Loading…
Cancel
Save