diff --git a/internal/acceptance/bake/bake_test.go b/internal/acceptance/bake/bake_test.go index 5c5d25bac..e83e86750 100644 --- a/internal/acceptance/bake/bake_test.go +++ b/internal/acceptance/bake/bake_test.go @@ -532,7 +532,7 @@ var _ = Describe("bake command", func() { } Expect(emptyMigrationsFolderMode.IsDir()).To(BeTrue()) - Expect(emptyMigrationsFolderModified).To(BeTemporally("~", builder.ZipHeaderModifiedDate(), time.Minute)) + Expect(emptyMigrationsFolderModified).NotTo(BeZero()) Eventually(session.Err).Should(gbytes.Say(fmt.Sprintf("Creating empty migrations folder in %s...", outputFile))) }) diff --git a/internal/acceptance/workflows/baking_a_tile.feature b/internal/acceptance/workflows/baking_a_tile.feature index b343d70dd..1067fc72d 100644 --- a/internal/acceptance/workflows/baking_a_tile.feature +++ b/internal/acceptance/workflows/baking_a_tile.feature @@ -15,7 +15,7 @@ Feature: As a developer, I want to bake a tile And "bake_records/0.2.0-dev.json" contains substring: "version": "0.2.0-dev" And "bake_records/0.2.0-dev.json" contains substring: "source_revision": "bc3ac24e192ba06a2eca19381ad785ec7069e0d0" And "bake_records/0.2.0-dev.json" contains substring: "kiln_version": "0.0.0+acceptance-tests" - And "tile-0.2.0-dev.pivotal" has sha256 sum "832de6c6ae1d0a0d0a318f093e01e3f80b17ef7c186f3e9386cca3194b41bb44" + And "tile-0.2.0-dev.pivotal" has sha256 sum "ec0f718ffcf2f066024b4bb6ae0249677e641c4753e28b0ca6d8b8ea6b2e29c5" Scenario: it reads directory configuration from Kilnfile Given I have a tile source directory "testdata/tiles/non-standard-paths" diff --git a/internal/builder/fakes/zipper.go b/internal/builder/fakes/zipper.go index d592c10a8..49e02b439 100644 --- a/internal/builder/fakes/zipper.go +++ b/internal/builder/fakes/zipper.go @@ -5,6 +5,7 @@ import ( "io" "io/fs" "sync" + "time" ) type Zipper struct { @@ -54,6 +55,11 @@ type Zipper struct { createFolderReturnsOnCall map[int]struct { result1 error } + SetModifiedStub func(time.Time) + setModifiedMutex sync.RWMutex + setModifiedArgsForCall []struct { + arg1 time.Time + } SetWriterStub func(io.Writer) setWriterMutex sync.RWMutex setWriterArgsForCall []struct { @@ -302,6 +308,38 @@ func (fake *Zipper) CreateFolderReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *Zipper) SetModified(arg1 time.Time) { + fake.setModifiedMutex.Lock() + fake.setModifiedArgsForCall = append(fake.setModifiedArgsForCall, struct { + arg1 time.Time + }{arg1}) + stub := fake.SetModifiedStub + fake.recordInvocation("SetModified", []interface{}{arg1}) + fake.setModifiedMutex.Unlock() + if stub != nil { + fake.SetModifiedStub(arg1) + } +} + +func (fake *Zipper) SetModifiedCallCount() int { + fake.setModifiedMutex.RLock() + defer fake.setModifiedMutex.RUnlock() + return len(fake.setModifiedArgsForCall) +} + +func (fake *Zipper) SetModifiedCalls(stub func(time.Time)) { + fake.setModifiedMutex.Lock() + defer fake.setModifiedMutex.Unlock() + fake.SetModifiedStub = stub +} + +func (fake *Zipper) SetModifiedArgsForCall(i int) time.Time { + fake.setModifiedMutex.RLock() + defer fake.setModifiedMutex.RUnlock() + argsForCall := fake.setModifiedArgsForCall[i] + return argsForCall.arg1 +} + func (fake *Zipper) SetWriter(arg1 io.Writer) { fake.setWriterMutex.Lock() fake.setWriterArgsForCall = append(fake.setWriterArgsForCall, struct { @@ -345,6 +383,8 @@ func (fake *Zipper) Invocations() map[string][][]interface{} { defer fake.closeMutex.RUnlock() fake.createFolderMutex.RLock() defer fake.createFolderMutex.RUnlock() + fake.setModifiedMutex.RLock() + defer fake.setModifiedMutex.RUnlock() fake.setWriterMutex.RLock() defer fake.setWriterMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/internal/builder/metadata_git_sha.go b/internal/builder/metadata_git_sha.go index a6ff9c1d5..a55cb8258 100644 --- a/internal/builder/metadata_git_sha.go +++ b/internal/builder/metadata_git_sha.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "os/exec" + "strconv" "strings" + "time" ) const DirtyWorktreeSHAValue = "DEVELOPMENT" @@ -14,17 +16,55 @@ func GitMetadataSHA(repositoryDirectory string, isDev bool) (string, error) { if err := ensureGitExecutableIsFound(); err != nil { return "", err } + if dirty, err := GitStateIsDirty(repositoryDirectory); err != nil { + return "", err + } else if dirty && isDev { + _, _ = fmt.Fprintf(os.Stderr, "WARNING: git working directory has un-commited changes: the variable %q has has development only value %q", MetadataGitSHAVariable, DirtyWorktreeSHAValue) + return DirtyWorktreeSHAValue, nil + } + return gitHeadRevision(repositoryDirectory) +} + +func ModifiedTime(repositoryDirectory string, isDev bool) (time.Time, error) { + if isDev { + return time.Now(), nil + } + if err := ensureGitExecutableIsFound(); err != nil { + return time.Time{}, err + } + if dirty, err := GitStateIsDirty(repositoryDirectory); err != nil { + return time.Time{}, err + } else if dirty && isDev { + return time.Now(), nil + } + return GitCommitterCommitDate(repositoryDirectory) +} + +func GitStateIsDirty(repositoryDirectory string) (bool, error) { gitStatus := exec.Command("git", "status", "--porcelain") gitStatus.Dir = repositoryDirectory err := gitStatus.Run() if err != nil { - if gitStatus.ProcessState.ExitCode() == 1 && isDev { - _, _ = fmt.Fprintf(os.Stderr, "WARNING: git working directory has un-commited changes: the variable %q has has development only value %q", MetadataGitSHAVariable, DirtyWorktreeSHAValue) - return DirtyWorktreeSHAValue, nil + if gitStatus.ProcessState.ExitCode() == 1 { + return true, nil } - return "", fmt.Errorf("failed to run `%s %s`: %w", gitStatus.Path, strings.Join(gitStatus.Args, " "), err) + return true, fmt.Errorf("failed to run `%s %s`: %w", gitStatus.Path, strings.Join(gitStatus.Args, " "), err) } - return gitHeadRevision(repositoryDirectory) + return false, nil +} + +func GitCommitterCommitDate(repositoryDirectory string) (time.Time, error) { + cmd := exec.Command("git", "show", "-s", "--format=%ct") + cmd.Dir = repositoryDirectory + output, err := cmd.Output() + if err != nil { + return time.Time{}, err + } + commitTime, err := strconv.ParseInt(strings.TrimSpace(string(output)), 10, 64) + if err != nil { + return time.Time{}, err + } + return time.Unix(commitTime, 0), nil } func gitHeadRevision(repositoryDirectory string) (string, error) { diff --git a/internal/builder/tile_writer.go b/internal/builder/tile_writer.go index c77507b23..86f202a33 100644 --- a/internal/builder/tile_writer.go +++ b/internal/builder/tile_writer.go @@ -8,6 +8,7 @@ import ( "path/filepath" "regexp" "strings" + "time" "gopkg.in/yaml.v2" ) @@ -30,6 +31,7 @@ type filesystem interface { type zipper interface { SetWriter(writer io.Writer) + SetModified(t time.Time) Add(path string, file io.Reader) error AddWithMode(path string, file io.Reader, mode os.FileMode) error CreateFolder(path string) error @@ -52,6 +54,7 @@ type WriteInput struct { MigrationDirectories []string ReleaseDirectories []string EmbedPaths []string + ModTime time.Time } type tileMetadata struct { @@ -72,6 +75,7 @@ func (w TileWriter) Write(generatedMetadataContents []byte, input WriteInput) er defer closeAndIgnoreError(f) w.zipper.SetWriter(f) + w.zipper.SetModified(input.ModTime) err = w.addToZipper(path.Join("metadata", "metadata.yml"), bytes.NewBuffer(generatedMetadataContents), input.OutputFile) if err != nil { diff --git a/internal/builder/tile_writer_test.go b/internal/builder/tile_writer_test.go index 56c350428..1cd98f540 100644 --- a/internal/builder/tile_writer_test.go +++ b/internal/builder/tile_writer_test.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" @@ -26,6 +27,8 @@ var _ = Describe("TileWriter", func() { outputFile string expectedFile *os.File + + someTime time.Time ) BeforeEach(func() { @@ -34,6 +37,7 @@ var _ = Describe("TileWriter", func() { logger = &fakes.Logger{} tileWriter = builder.NewTileWriter(filesystem, zipper, logger) outputFile = "some-output-dir/cool-product-file-1.2.3-build.4.pivotal" + someTime = time.Date(2018, 4, 20, 0, 0, 0, 0, time.UTC) }) Describe("Write", func() { @@ -48,6 +52,7 @@ var _ = Describe("TileWriter", func() { MigrationDirectories: []string{"/some/path/migrations", "/some/other/path/migrations"}, OutputFile: outputFile, StubReleases: stubbed, + ModTime: someTime, } dirInfo := &fakes.FileInfo{} @@ -217,11 +222,14 @@ releases: ReleaseDirectories: []string{"/some/path/releases"}, OutputFile: "some-output-dir/cool-product-file-1.2.3-build.4.pivotal", StubReleases: true, + ModTime: someTime, } err := tileWriter.Write([]byte("releases:\n- file: release-1.tgz"), input) Expect(err).NotTo(HaveOccurred()) Expect(zipper.AddCallCount()).To(Equal(2)) + Expect(zipper.SetModifiedCallCount()).To(Equal(1)) + Expect(zipper.SetModifiedArgsForCall(0).Equal(someTime)).To(BeTrue()) path, _ := zipper.AddArgsForCall(1) Expect(path).To(Equal(filepath.Join("releases", "release-1.tgz"))) }) diff --git a/internal/builder/zipper.go b/internal/builder/zipper.go index aaef7bdae..ca3e34cf8 100644 --- a/internal/builder/zipper.go +++ b/internal/builder/zipper.go @@ -12,6 +12,7 @@ import ( type Zipper struct { writer *zip.Writer + mod time.Time } func NewZipper() Zipper { @@ -22,8 +23,8 @@ func (z *Zipper) SetWriter(writer io.Writer) { z.writer = zip.NewWriter(writer) } -func ZipHeaderModifiedDate() time.Time { - return time.Date(2018, 4, 20, 0, 0, 0, 0, time.UTC) +func (z *Zipper) SetModified(mod time.Time) { + z.mod = mod } func (z Zipper) Add(path string, file io.Reader) error { @@ -34,7 +35,7 @@ func (z Zipper) Add(path string, file io.Reader) error { return z.add(&zip.FileHeader{ Name: path, Method: zip.Store, - Modified: ZipHeaderModifiedDate(), + Modified: z.mod, }, file) } @@ -46,7 +47,7 @@ func (z Zipper) AddWithMode(path string, file io.Reader, mode os.FileMode) error fh := &zip.FileHeader{ Name: path, Method: zip.Store, - Modified: ZipHeaderModifiedDate(), + Modified: z.mod, } fh.SetMode(mode) @@ -80,7 +81,7 @@ func (z Zipper) CreateFolder(path string) error { fh := &zip.FileHeader{ Name: path, - Modified: ZipHeaderModifiedDate(), + Modified: z.mod, } _, err := z.writer.CreateHeader(fh) if err != nil { diff --git a/internal/builder/zipper_test.go b/internal/builder/zipper_test.go index 22d0b3851..c75aaac79 100644 --- a/internal/builder/zipper_test.go +++ b/internal/builder/zipper_test.go @@ -46,6 +46,7 @@ var _ = Describe("Zipper", func() { It("creates the given path", func() { zipper := builder.NewZipper() zipper.SetWriter(tileFile) + zipper.SetModified(someDate) err := zipper.CreateFolder("some/path/to/folder") Expect(err).NotTo(HaveOccurred()) @@ -59,12 +60,13 @@ var _ = Describe("Zipper", func() { Expect(reader.File).To(HaveLen(1)) Expect(reader.File[0].Name).To(Equal("some/path/to/folder/")) Expect(reader.File[0].Mode().IsDir()).To(BeTrue()) - Expect(reader.File[0].FileHeader.Modified).To(BeTemporally("~", builder.ZipHeaderModifiedDate(), time.Minute)) + Expect(reader.File[0].FileHeader.Modified.Equal(someDate)).To(BeTrue()) }) It("does not append separator if already given to the input", func() { zipper := builder.NewZipper() zipper.SetWriter(tileFile) + zipper.SetModified(someDate) err := zipper.CreateFolder("some/path/to/folder/") Expect(err).NotTo(HaveOccurred()) @@ -78,6 +80,7 @@ var _ = Describe("Zipper", func() { Expect(reader.File).To(HaveLen(1)) Expect(reader.File[0].Name).To(Equal("some/path/to/folder/")) Expect(reader.File[0].Mode().IsDir()).To(BeTrue()) + Expect(reader.File[0].FileHeader.Modified.Equal(someDate)).To(BeTrue()) }) Context("failure cases", func() { @@ -96,6 +99,7 @@ var _ = Describe("Zipper", func() { It("writes the given file into the path", func() { zipper := builder.NewZipper() zipper.SetWriter(tileFile) + zipper.SetModified(someDate) err := zipper.Add("some/path/to/file.txt", strings.NewReader("file contents")) Expect(err).NotTo(HaveOccurred()) @@ -117,7 +121,7 @@ var _ = Describe("Zipper", func() { Expect(contents).To(Equal([]byte("file contents"))) Expect(reader.File[0].FileHeader.Mode()).To(Equal(os.FileMode(0o666))) - Expect(reader.File[0].FileHeader.Modified).To(BeTemporally("~", someDate, time.Minute)) + Expect(reader.File[0].FileHeader.Modified.Equal(someDate)).To(BeTrue()) }) Context("failure cases", func() { @@ -149,6 +153,7 @@ var _ = Describe("Zipper", func() { It("writes the given file into the path", func() { zipper := builder.NewZipper() zipper.SetWriter(tileFile) + zipper.SetModified(someDate) err := zipper.AddWithMode("some/path/to/file.txt", strings.NewReader("file contents"), 0o644) Expect(err).NotTo(HaveOccurred()) @@ -170,7 +175,7 @@ var _ = Describe("Zipper", func() { Expect(contents).To(Equal([]byte("file contents"))) Expect(reader.File[0].FileHeader.Mode()).To(Equal(os.FileMode(0o644))) - Expect(reader.File[0].FileHeader.Modified).To(BeTemporally("~", someDate, time.Minute)) + Expect(reader.File[0].FileHeader.Modified.Equal(someDate)).To(BeTrue()) }) Context("failure cases", func() { diff --git a/internal/commands/bake.go b/internal/commands/bake.go index eb69a02b9..fc4292179 100644 --- a/internal/commands/bake.go +++ b/internal/commands/bake.go @@ -498,11 +498,17 @@ func (b Bake) Execute(args []string) error { return fmt.Errorf("failed to read metadata: %s", err) } - gitMetadataSHA, err := builder.GitMetadataSHA(filepath.Dir(b.Options.Kilnfile), b.Options.MetadataOnly || b.Options.StubReleases) + isDevBuild := b.Options.MetadataOnly || b.Options.StubReleases + gitMetadataSHA, err := builder.GitMetadataSHA(filepath.Dir(b.Options.Kilnfile), isDevBuild) if err != nil { return fmt.Errorf("failed to read metadata: %s", err) } + modTime, err := builder.ModifiedTime(filepath.Dir(b.Options.Kilnfile), isDevBuild) + if err != nil { + return fmt.Errorf("failed to read modified date from commit: %s", err) + } + input := builder.InterpolateInput{ KilnVersion: b.KilnVersion, Version: b.Options.Version, @@ -542,6 +548,7 @@ func (b Bake) Execute(args []string) error { MigrationDirectories: b.Options.MigrationDirectories, ReleaseDirectories: b.Options.ReleaseDirectories, EmbedPaths: b.Options.EmbedPaths, + ModTime: modTime, }) if err != nil { return err diff --git a/internal/commands/bake_test.go b/internal/commands/bake_test.go index f8384a964..a7d578e5e 100644 --- a/internal/commands/bake_test.go +++ b/internal/commands/bake_test.go @@ -8,8 +8,7 @@ import ( "reflect" "strings" "testing" - - "github.com/pivotal-cf/kiln/pkg/source" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -22,6 +21,7 @@ import ( "github.com/pivotal-cf/kiln/internal/commands" "github.com/pivotal-cf/kiln/internal/commands/fakes" "github.com/pivotal-cf/kiln/pkg/proofing" + "github.com/pivotal-cf/kiln/pkg/source" ) var _ = Describe("Bake", func() { @@ -347,6 +347,10 @@ var _ = Describe("Bake", func() { Expect(fakeTileWriter.WriteCallCount()).To(Equal(1)) metadata, writeInput := fakeTileWriter.WriteArgsForCall(0) Expect(string(metadata)).To(Equal("some-interpolated-metadata")) + + Expect(writeInput.ModTime).NotTo(BeZero()) + writeInput.ModTime = time.Time{} + Expect(writeInput).To(Equal(builder.WriteInput{ OutputFile: filepath.Join("some-output-dir", "some-product-file-1.2.3-build.4"), StubReleases: false,