Merge branch 'helm:main' into feature/enhance-dry-run-for-helm-4

pull/31616/head
MrJack 3 weeks ago committed by GitHub
commit f332d3b69c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,9 +17,14 @@ limitations under the License.
package action
import (
"archive/tar"
"compress/gzip"
"errors"
"io"
"os"
"path"
"testing"
"time"
"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert"
@ -144,3 +149,60 @@ func TestRun(t *testing.T) {
require.Equal(t, "empty-0.1.0.tgz", filename)
require.NoError(t, os.Remove(filename))
}
// TestRunWithSourceDateEpochStampsLockGenerated verifies that packaging a chart
// that has a Chart.lock stamps both the tar entry modtime and the marshaled
// generated: field in Chart.lock to the given epoch.
//
// This guards against the normalization regression where a caller supplying a
// local-timezone or sub-second time.Time would produce a non-reproducible
// generated: value even when the same SOURCE_DATE_EPOCH is used on different
// machines.
func TestRunWithSourceDateEpochStampsLockGenerated(t *testing.T) {
// Use a non-local, non-UTC timezone and sub-second precision to confirm
// normalization: without UTC().Truncate(time.Second) the generated: field
// would contain a timezone offset or fractional seconds.
loc := time.FixedZone("UTC+3", 3*60*60)
rawEpoch := time.Unix(1700000000, 123456789).In(loc)
epoch := rawEpoch.UTC().Truncate(time.Second)
client := NewPackage()
client.SourceDateEpoch = &rawEpoch
filename, err := client.Run("testdata/charts/chart-with-lock", nil)
require.NoError(t, err)
t.Cleanup(func() { os.Remove(filename) })
f, err := os.Open(filename)
require.NoError(t, err)
defer f.Close()
gr, err := gzip.NewReader(f)
require.NoError(t, err)
defer gr.Close()
const wantPath = "chart-with-lock/Chart.lock"
found := false
tr := tar.NewReader(gr)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
require.NoError(t, err)
if hdr.Name != wantPath {
continue
}
found = true
require.True(t, epoch.Equal(hdr.ModTime),
"Chart.lock tar modtime: got %v, want %v", hdr.ModTime, epoch)
raw, err := io.ReadAll(tr)
require.NoError(t, err)
wantGenerated := epoch.Format(time.RFC3339)
require.Contains(t, string(raw), wantGenerated,
"Chart.lock generated: field should contain normalized UTC timestamp")
break
}
require.True(t, found, "expected archive to contain %q entry", wantPath)
}

@ -0,0 +1,3 @@
dependencies: []
digest: sha256:0000000000000000000000000000000000000000000000000000000000000
generated: "2016-01-01T00:00:00Z"

@ -0,0 +1,4 @@
apiVersion: v2
name: chart-with-lock
version: 0.1.0
description: Test chart with a Chart.lock, used to verify SourceDateEpoch stamping

@ -177,34 +177,41 @@ func (ch *Chart) CRDObjects() []CRD {
return crds
}
// StampModTimes sets timestamps on the chart (and dependencies) to epoch.
// This is used for reproducible builds via SOURCE_DATE_EPOCH.
func (ch *Chart) StampModTimes(epoch time.Time) {
ch.ModTime = epoch
// StampModTimes sets timestamps on the chart (and dependencies) to t,
// normalized to UTC and truncated to whole seconds.
//
// Normalization is required because Chart.lock's generated: field is written
// by yaml.Marshal from Lock.Generated. Without UTC/truncate, a caller
// supplying a local-zone or sub-second time.Time produces a generated: value
// with a timezone offset or fractional seconds, making the lock file content
// non-reproducible across machines even when the same SOURCE_DATE_EPOCH is used.
func (ch *Chart) StampModTimes(t time.Time) {
t = t.UTC().Truncate(time.Second)
ch.ModTime = t
if len(ch.Schema) > 0 {
ch.SchemaModTime = epoch
ch.SchemaModTime = t
}
if ch.Lock != nil {
ch.Lock.Generated = epoch
ch.Lock.Generated = t
}
for _, f := range ch.Raw {
if f != nil {
f.ModTime = epoch
f.ModTime = t
}
}
for _, f := range ch.Templates {
if f != nil {
f.ModTime = epoch
f.ModTime = t
}
}
for _, f := range ch.Files {
if f != nil {
f.ModTime = epoch
f.ModTime = t
}
}
for _, dep := range ch.Dependencies() {
dep.StampModTimes(epoch)
dep.StampModTimes(t)
}
}

Loading…
Cancel
Save