diff --git a/internal/chart/v3/util/epoch.go b/internal/chart/v3/util/epoch.go index f043be6d1..1a9b293be 100644 --- a/internal/chart/v3/util/epoch.go +++ b/internal/chart/v3/util/epoch.go @@ -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) diff --git a/internal/chart/v3/util/epoch_test.go b/internal/chart/v3/util/epoch_test.go index 7fd9f37ef..b2d58b0c5 100644 --- a/internal/chart/v3/util/epoch_test.go +++ b/internal/chart/v3/util/epoch_test.go @@ -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) { diff --git a/pkg/chart/v2/util/epoch.go b/pkg/chart/v2/util/epoch.go index f65e8c3ee..67367a0c6 100644 --- a/pkg/chart/v2/util/epoch.go +++ b/pkg/chart/v2/util/epoch.go @@ -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) diff --git a/pkg/chart/v2/util/epoch_test.go b/pkg/chart/v2/util/epoch_test.go index 5a47c2589..89012d417 100644 --- a/pkg/chart/v2/util/epoch_test.go +++ b/pkg/chart/v2/util/epoch_test.go @@ -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) { diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 5104efbad..f5fed6b11 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -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)