From 984448e2293f7d772232e08fc30771aac36730fc Mon Sep 17 00:00:00 2001 From: Matthias Diester Date: Fri, 12 Jan 2024 15:37:17 +0100 Subject: [PATCH] Add image and source timestamp results for bundle Add flag to write bundle image timestamp into result file. Add flag to write source timestamp into result file. Fix `PackAndPush` function to set timestamp of the base image and not for all files in the main layer of the image to keep the timestamps of the files in the bundle layer. --- cmd/bundle/main.go | 32 +++++++++++++--- cmd/bundle/main_test.go | 85 ++++++++++++++++++++++++++++++++++++++++- pkg/bundle/bundle.go | 4 +- 3 files changed, 111 insertions(+), 10 deletions(-) diff --git a/cmd/bundle/main.go b/cmd/bundle/main.go index 21a5c09399..793a3cd097 100644 --- a/cmd/bundle/main.go +++ b/cmd/bundle/main.go @@ -9,21 +9,24 @@ import ( "fmt" "log" "os" + "strconv" "github.com/google/go-containerregistry/pkg/name" "github.com/spf13/pflag" "github.com/shipwright-io/build/pkg/bundle" + "github.com/shipwright-io/build/pkg/filesystem" "github.com/shipwright-io/build/pkg/image" ) type settings struct { - help bool - image string - prune bool - target string - secretPath string - resultFileImageDigest string + help bool + image string + prune bool + target string + secretPath string + resultFileImageDigest string + resultFileSourceTimestamp string } var flagValues settings @@ -36,6 +39,7 @@ func init() { pflag.StringVar(&flagValues.image, "image", "", "Location of the bundle image (mandatory)") pflag.StringVar(&flagValues.target, "target", "/workspace/source", "The target directory to place the code") pflag.StringVar(&flagValues.resultFileImageDigest, "result-file-image-digest", "", "A file to write the image digest") + pflag.StringVar(&flagValues.resultFileSourceTimestamp, "result-file-source-timestamp", "", "A file to write the source timestamp") pflag.StringVar(&flagValues.secretPath, "secret-path", "", "A directory that contains access credentials (optional)") pflag.BoolVar(&flagValues.prune, "prune", false, "Delete bundle image from registry after it was pulled") @@ -93,6 +97,22 @@ func Do(ctx context.Context) error { } } + if flagValues.resultFileSourceTimestamp != "" { + timestamp, err := filesystem.MostRecentFileTimestamp(flagValues.target) + if err != nil { + return err + } + + if timestamp != nil { + if err = os.WriteFile(flagValues.resultFileSourceTimestamp, []byte(strconv.FormatInt(timestamp.Unix(), 10)), 0644); err != nil { + return err + } + + } else { + log.Printf("Unable to determine source timestamp of content in %s\n", flagValues.target) + } + } + if flagValues.prune { // Some container registry implementations, i.e. library/registry:2 will fail to // delete the image when there is no image digest given. Use image digest from the diff --git a/cmd/bundle/main_test.go b/cmd/bundle/main_test.go index a7f8c366dc..debc653ff1 100644 --- a/cmd/bundle/main_test.go +++ b/cmd/bundle/main_test.go @@ -9,16 +9,21 @@ import ( "fmt" "io" "log" + "net/http/httptest" + "net/url" "os" "path/filepath" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/shipwright-io/build/cmd/bundle" + "github.com/shipwright-io/build/pkg/bundle" "github.com/shipwright-io/build/pkg/image" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" containerreg "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "k8s.io/apimachinery/pkg/util/rand" @@ -27,7 +32,7 @@ import ( var _ = Describe("Bundle Loader", func() { const exampleImage = "ghcr.io/shipwright-io/sample-go/source-bundle:latest" - var run = func(args ...string) error { + run := func(args ...string) error { // discard log output log.SetOutput(io.Discard) @@ -40,7 +45,7 @@ var _ = Describe("Bundle Loader", func() { return Do(context.Background()) } - var withTempDir = func(f func(target string)) { + withTempDir := func(f func(target string)) { path, err := os.MkdirTemp(os.TempDir(), "bundle") Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(path) @@ -56,6 +61,24 @@ var _ = Describe("Bundle Loader", func() { f(file.Name()) } + withTempRegistry := func(f func(endpoint string)) { + logLogger := log.Logger{} + logLogger.SetOutput(GinkgoWriter) + + s := httptest.NewServer( + registry.New( + registry.Logger(&logLogger), + registry.WithReferrersSupport(true), + ), + ) + defer s.Close() + + u, err := url.Parse(s.URL) + Expect(err).ToNot(HaveOccurred()) + + f(u.Host) + } + filecontent := func(path string) string { data, err := os.ReadFile(path) Expect(err).ToNot(HaveOccurred()) @@ -234,4 +257,62 @@ var _ = Describe("Bundle Loader", func() { }) }) }) + + Context("Result file checks", func() { + tmpFile := func(dir string, name string, data []byte, timestamp time.Time) { + var path = filepath.Join(dir, name) + + Expect(os.WriteFile( + path, + data, + os.FileMode(0644), + )).To(Succeed()) + + Expect(os.Chtimes( + path, + timestamp, + timestamp, + )).To(Succeed()) + } + + // Creates a controlled reference image with one file called "file" with modification + // timestamp of Friday, February 13, 2009 11:31:30 PM (unix timestamp 1234567890) + withReferenceImage := func(f func(dig name.Digest)) { + withTempRegistry(func(endpoint string) { + withTempDir(func(target string) { + timestamp := time.Unix(1234567890, 0) + + ref, err := name.ParseReference(fmt.Sprintf("%s/namespace/image:tag", endpoint)) + Expect(err).ToNot(HaveOccurred()) + Expect(ref).ToNot(BeNil()) + + tmpFile(target, "file", []byte("foobar"), timestamp) + + dig, err := bundle.PackAndPush(ref, target) + Expect(err).ToNot(HaveOccurred()) + Expect(dig).ToNot(BeNil()) + + f(dig) + }) + }) + } + + It("should store bundle image timestamp and source timestamp in result files", func() { + withTempDir(func(target string) { + withTempDir(func(result string) { + withReferenceImage(func(dig name.Digest) { + resultSourceTimestamp := filepath.Join(result, "source-timestamp") + + Expect(run( + "--image", dig.String(), + "--target", target, + "--result-file-source-timestamp", resultSourceTimestamp, + )).To(Succeed()) + + Expect(filecontent(resultSourceTimestamp)).To(Equal("1234567890")) + }) + }) + }) + }) + }) }) diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index 2a32ea78ba..fbe0bf36c5 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -35,12 +35,12 @@ func PackAndPush(ref name.Reference, directory string, options ...remote.Option) return name.Digest{}, err } - image, err := mutate.AppendLayers(empty.Image, bundleLayer) + image, err := mutate.Time(empty.Image, time.Unix(0, 0)) if err != nil { return name.Digest{}, err } - image, err = mutate.Time(image, time.Unix(0, 0)) + image, err = mutate.AppendLayers(image, bundleLayer) if err != nil { return name.Digest{}, err }