From a0c33d0f15576460f5fc459e2484dce7da60f7b3 Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Wed, 8 May 2024 15:17:40 -0400 Subject: [PATCH] Add new function CreateJar * for now used in spring-boot buildpack --- crush/crush.go | 80 ++++++++++++++++++ crush/crush_test.go | 26 ++++++ .../testdata/spring-cloud-bindings-1.2.3.jar | Bin 0 -> 333 bytes 3 files changed, 106 insertions(+) create mode 100644 crush/testdata/spring-cloud-bindings-1.2.3.jar diff --git a/crush/crush.go b/crush/crush.go index e666c1c..2afe915 100644 --- a/crush/crush.go +++ b/crush/crush.go @@ -103,6 +103,86 @@ func CreateTarGz(destination io.Writer, source string) error { return CreateTar(gz, source) } +// CreateJar heavily inspired by: https://gosamples.dev/zip-file/ +// Be aware that this function does not create a MANIFEST.MF file, not does it strictly enforce jar format +// in regard to elements that need to be STORE'd versus other that need to be DEFLATE'd; here everything is STORE'd +// Finally, source path must end with a trailing "/" +func CreateJar(source, target string) error { + + // 1. Create a ZIP file and zip.Writer + f, err := os.Create(target) + if err != nil { + return err + } + defer f.Close() + + writer := zip.NewWriter(f) + defer writer.Close() + + // 2. Go through all the files of the source + return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + absolutePath := "" + + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + if absolutePath, err = filepath.EvalSymlinks(path); err != nil { + return fmt.Errorf("unable to eval symlink %s\n%w", absolutePath, err) + } + if file, err := os.Open(absolutePath); err != nil { + return fmt.Errorf("unable to open %s\n%w", absolutePath, err) + } else { + if info, err = file.Stat(); err != nil { + return fmt.Errorf("unable to stat %s\n%w", absolutePath, err) + } + } + } + + // 3. Create a local file header + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // set compression + header.Method = zip.Store + // 4. Set relative path of a file as the header name + header.Name, err = filepath.Rel(source, path) + if err != nil { + return err + } + if info.IsDir() { + header.Name += "/" + } + + // 5. Create writer for the file header and save content of the file + headerWriter, err := writer.CreateHeader(header) + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + if absolutePath != "" { + path = absolutePath + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(headerWriter, f) + writer.Flush() + return err + }) + +} + // Extract decompresses and extract source files to a destination directory or path. For archives, an arbitrary number of top-level directory // components can be stripped from each path. func Extract(source io.Reader, destination string, stripComponents int) error { diff --git a/crush/crush_test.go b/crush/crush_test.go index d006656..0ef8d84 100644 --- a/crush/crush_test.go +++ b/crush/crush_test.go @@ -95,6 +95,32 @@ func testCrush(t *testing.T, context spec.G, it spec.S) { Expect(filepath.Join(testPath, "dirA", "fileC.txt")).To(BeARegularFile()) Expect(os.Readlink(filepath.Join(testPath, "dirA", "fileD.txt"))).To(Equal(filepath.Join(path, "dirA", "fileC.txt"))) }) + + it("writes a JAR", func() { + cwd, _ := os.Getwd() + Expect(os.MkdirAll(filepath.Join(path, "META-INF"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(path, "META-INF", "MANIFEST.MF"), []byte(` + Spring-Boot-Version: 3.3.1 + Spring-Boot-Classes: BOOT-INF/classes + Spring-Boot-Lib: BOOT-INF/lib + `), 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF"), 0755)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "classes"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "OtherClass.class"), []byte(""), 0644)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "YetOther.class"), []byte(""), 0644)).To(Succeed()) + Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "lib"), 0755)).To(Succeed()) + os.Symlink(filepath.Join(cwd, "testdata", "spring-cloud-bindings-1.2.3.jar"), filepath.Join(path, "BOOT-INF", "lib", "spring-cloud-bindings-1.2.3.jar")) + + Expect(crush.CreateJar(path+"/", out.Name()+".jar")).To(Succeed()) + + in, err := os.Open(out.Name() + ".jar") + Expect(err).NotTo(HaveOccurred()) + + Expect(crush.Extract(in, testPath, 0)).To(Succeed()) + Expect(filepath.Join(testPath, "BOOT-INF", "classes", "OtherClass.class")).To(BeARegularFile()) + Expect(filepath.Join(testPath, "META-INF", "MANIFEST.MF")).To(BeARegularFile()) + Expect(filepath.Join(testPath, "BOOT-INF", "lib", "spring-cloud-bindings-1.2.3.jar")).To(BeARegularFile()) + }) }) context("Extract", func() { diff --git a/crush/testdata/spring-cloud-bindings-1.2.3.jar b/crush/testdata/spring-cloud-bindings-1.2.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..f83509d908827effb980313fc4845d1b0700f645 GIT binary patch literal 333 zcmWIWW@Zs#;Nak3(94=0z<>le8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gPCoIpGPtEvL^s$H$Z24UG219G0(I6b)&B!Fefa-Rb!$59F1#riJ ke1fhO*;tUv5x^G6glk3eM}Rjg8%Pxs5Y__eT_6qv02;49i2wiq literal 0 HcmV?d00001