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 1 month ago
parent f2d339dfa4
commit 81c8a2d844
No known key found for this signature in database
GPG Key ID: 532A7B7866CFDC51

@ -47,36 +47,29 @@ func ParseSourceDateEpoch() (time.Time, error) {
return time.Unix(epoch, 0), nil
}
// ApplySourceDateEpoch sets the ModTime on the chart and all of its entries
// that currently have a zero ModTime to t. It recurses into dependencies.
// ApplySourceDateEpoch overrides the ModTime on the chart and all of its
// 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.
func ApplySourceDateEpoch(c *chart.Chart, t time.Time) {
if t.IsZero() {
return
}
if c.ModTime.IsZero() {
c.ModTime = t
}
if c.Lock != nil && c.Lock.Generated.IsZero() {
c.ModTime = t
if c.Lock != nil {
c.Lock.Generated = t
}
if c.Schema != nil && c.SchemaModTime.IsZero() {
if c.Schema != nil {
c.SchemaModTime = t
}
for _, f := range c.Raw {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, f := range c.Templates {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, f := range c.Files {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, dep := range c.Dependencies() {
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)
existing := time.Unix(1600000000, 0)
@ -133,12 +133,28 @@ func TestApplySourceDateEpochPreservesExisting(t *testing.T) {
Version: "0.1.0",
},
ModTime: existing,
Templates: []*common.File{
{Name: "templates/test.yaml", ModTime: existing},
},
Files: []*common.File{
{Name: "README.md", ModTime: existing},
},
}
ApplySourceDateEpoch(c, epoch)
if !c.ModTime.Equal(existing) {
t.Errorf("Chart.ModTime = %v, want existing %v", c.ModTime, existing)
if !c.ModTime.Equal(epoch) {
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)
// Parent chart already had a ModTime, so it should be preserved.
if !c.ModTime.Equal(existing) {
t.Errorf("parent Chart.ModTime = %v, want existing %v", c.ModTime, existing)
// Parent chart had an existing ModTime, but it should be overridden.
if !c.ModTime.Equal(epoch) {
t.Errorf("parent Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
}
// Dependency had a zero ModTime, so it should be stamped.
if !dep.ModTime.Equal(epoch) {

@ -47,36 +47,29 @@ func ParseSourceDateEpoch() (time.Time, error) {
return time.Unix(epoch, 0), nil
}
// ApplySourceDateEpoch sets the ModTime on the chart and all of its entries
// that currently have a zero ModTime to t. It recurses into dependencies.
// ApplySourceDateEpoch overrides the ModTime on the chart and all of its
// 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.
func ApplySourceDateEpoch(c *chart.Chart, t time.Time) {
if t.IsZero() {
return
}
if c.ModTime.IsZero() {
c.ModTime = t
}
if c.Lock != nil && c.Lock.Generated.IsZero() {
c.ModTime = t
if c.Lock != nil {
c.Lock.Generated = t
}
if c.Schema != nil && c.SchemaModTime.IsZero() {
if c.Schema != nil {
c.SchemaModTime = t
}
for _, f := range c.Raw {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, f := range c.Templates {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, f := range c.Files {
if f.ModTime.IsZero() {
f.ModTime = t
}
f.ModTime = t
}
for _, dep := range c.Dependencies() {
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)
existing := time.Unix(1600000000, 0)
@ -133,12 +133,28 @@ func TestApplySourceDateEpochPreservesExisting(t *testing.T) {
Version: "0.1.0",
},
ModTime: existing,
Templates: []*common.File{
{Name: "templates/test.yaml", ModTime: existing},
},
Files: []*common.File{
{Name: "README.md", ModTime: existing},
},
}
ApplySourceDateEpoch(c, epoch)
if !c.ModTime.Equal(existing) {
t.Errorf("Chart.ModTime = %v, want existing %v", c.ModTime, existing)
if !c.ModTime.Equal(epoch) {
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)
// Parent chart already had a ModTime, so it should be preserved.
if !c.ModTime.Equal(existing) {
t.Errorf("parent Chart.ModTime = %v, want existing %v", c.ModTime, existing)
// Parent chart had an existing ModTime, but it should be overridden.
if !c.ModTime.Equal(epoch) {
t.Errorf("parent Chart.ModTime = %v, want epoch %v", c.ModTime, epoch)
}
// Dependency had a zero ModTime, so it should be stamped.
if !dep.ModTime.Equal(epoch) {

@ -304,7 +304,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
if m.Debug {
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 {
saveError = err
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
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://") {
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.
epoch, epochErr := chartutil.ParseSourceDateEpoch()
if epochErr != nil {
fmt.Fprintf(os.Stderr, "WARNING: %v\n", epochErr)
fmt.Fprintf(out, "WARNING: %v\n", epochErr)
}
chartutil.ApplySourceDateEpoch(ch, epoch)

Loading…
Cancel
Save