diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index 201372a0f..8a2746993 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "os" "path/filepath" + "time" "github.com/ghodss/yaml" @@ -205,9 +206,10 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { func writeToTar(out *tar.Writer, name string, body []byte) error { // TODO: Do we need to create dummy parent directory names if none exist? h := &tar.Header{ - Name: filepath.ToSlash(name), - Mode: 0755, - Size: int64(len(body)), + Name: filepath.ToSlash(name), + Mode: 0755, + Size: int64(len(body)), + ModTime: time.Now(), } if err := out.WriteHeader(h); err != nil { return err diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index 5e1564299..3279281b2 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -17,10 +17,14 @@ limitations under the License. package chartutil import ( + "archive/tar" + "compress/gzip" + "io" "io/ioutil" "os" "strings" "testing" + "time" "github.com/golang/protobuf/ptypes/any" "k8s.io/helm/pkg/proto/hapi/chart" @@ -73,6 +77,82 @@ func TestSave(t *testing.T) { } } +func TestSavePreservesTimestamps(t *testing.T) { + // Test executes so quickly that if we don't subtract a second, the + // check will fail because `initialCreateTime` will be identical to the + // written timestamp for the files. + initialCreateTime := time.Now().Add(-1 * time.Second) + + tmp, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "ahab", + Version: "1.2.3.4", + }, + Values: &chart.Config{ + Raw: "ship: Pequod", + }, + Files: []*any.Any{ + {TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")}, + }, + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + allHeaders, err := retrieveAllHeadersFromTar(where) + if err != nil { + t.Fatalf("Failed to parse tar: %v", err) + } + + for _, header := range allHeaders { + if header.ModTime.Before(initialCreateTime) { + t.Fatalf("File timestamp not preserved: %v", header.ModTime) + } + } +} + +// We could refactor `load.go` to use this `retrieveAllHeadersFromTar` function +// as well, so we are not duplicating components of the code which iterate +// through the tar. +func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) { + raw, err := os.Open(path) + if err != nil { + return nil, err + } + defer raw.Close() + + unzipped, err := gzip.NewReader(raw) + if err != nil { + return nil, err + } + defer unzipped.Close() + + tr := tar.NewReader(unzipped) + headers := []*tar.Header{} + for { + hd, err := tr.Next() + if err == io.EOF { + break + } + + if err != nil { + return nil, err + } + + headers = append(headers, hd) + } + + return headers, nil +} + func TestSaveDir(t *testing.T) { tmp, err := ioutil.TempDir("", "helm-") if err != nil {