Skip to content

Commit

Permalink
Merge pull request #376 from paketo-buildpacks/backports
Browse files Browse the repository at this point in the history
Backports
  • Loading branch information
dmikusa authored Nov 12, 2024
2 parents 4e3b6fe + 933d379 commit 796159f
Show file tree
Hide file tree
Showing 9 changed files with 983 additions and 59 deletions.
38 changes: 38 additions & 0 deletions buildmodule.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package libpak

import (
"fmt"
"net/url"
"os"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -501,6 +503,15 @@ func (d *DependencyResolver) Resolve(id string, version string) (BuildModuleDepe
return BuildModuleDependency{}, fmt.Errorf("unable to parse version %s\n%w", c.Version, err)
}

// filter out deps that do not match the current running architecture
arch, err := archFromPURL(c.PURL)
if err != nil {
return BuildModuleDependency{}, fmt.Errorf("unable to compare arch\n%w", err)
}
if arch != archFromSystem() {
continue
}

if c.ID == id && vc.Check(v) && d.contains(c.Stacks, d.StackID) {
candidates = append(candidates, c)
}
Expand Down Expand Up @@ -529,6 +540,33 @@ func (d *DependencyResolver) Resolve(id string, version string) (BuildModuleDepe
return candidate, nil
}

func archFromPURL(rawPURL string) (string, error) {
if len(strings.TrimSpace(rawPURL)) == 0 {
return "amd64", nil
}

purl, err := url.Parse(rawPURL)
if err != nil {
return "", fmt.Errorf("unable to parse PURL\n%w", err)
}

queryParams := purl.Query()
if arch, ok := queryParams["arch"]; ok {
return arch[0], nil
}

return archFromSystem(), nil
}

func archFromSystem() string {
archFromEnv, ok := os.LookupEnv("BP_ARCH")
if !ok {
archFromEnv = runtime.GOARCH
}

return archFromEnv
}

func (DependencyResolver) contains(candidates []string, value string) bool {
if len(candidates) == 0 {
return true
Expand Down
67 changes: 67 additions & 0 deletions buildmodule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
resolver libpak.DependencyResolver
)

it.Before(func() {
t.Setenv("BP_ARCH", "amd64") // force for test consistency
})

context("Resolve", func() {
it("filters by id", func() {
resolver.Dependencies = []libpak.BuildModuleDependency{
Expand Down Expand Up @@ -315,6 +319,69 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
}))
})

it("filters by arch", func() {
resolver.Dependencies = []libpak.BuildModuleDependency{
{
ID: "test-id-1",
Name: "test-name",
Version: "1.0",
URI: "test-uri-amd64",
SHA256: "test-sha256",
Stacks: []string{"test-stack-1", "test-stack-2"},
PURL: "pkg:generic/[email protected]?arch=amd64",
},
{
ID: "test-id-1",
Name: "test-name",
Version: "1.0",
URI: "test-uri-arm64",
SHA256: "test-sha256",
Stacks: []string{"test-stack-1", "test-stack-2"},
PURL: "pkg:generic/[email protected]?arch=arm64",
},
}
resolver.StackID = "test-stack-1"

t.Setenv("BP_ARCH", "arm64")

Expect(resolver.Resolve("test-id-1", "1.0")).To(Equal(libpak.BuildModuleDependency{
ID: "test-id-1",
Name: "test-name",
Version: "1.0",
URI: "test-uri-arm64",
SHA256: "test-sha256",
Stacks: []string{"test-stack-1", "test-stack-2"},
PURL: "pkg:generic/[email protected]?arch=arm64",
}))
})

it("filters by arch where arch should match any", func() {
resolver.Dependencies = []libpak.BuildModuleDependency{
{
ID: "test-id-1",
Name: "test-name",
Version: "1.0",
URI: "test-uri",
SHA256: "test-sha256",
Stacks: []string{"test-stack-1", "test-stack-2"},
PURL: "pkg:generic/[email protected]",
},
}
resolver.StackID = "test-stack-1"

t.Setenv("BP_ARCH", "arm64")

Expect(resolver.Resolve("test-id-1", "1.0")).To(Equal(libpak.BuildModuleDependency{
ID: "test-id-1",
Name: "test-name",
Version: "1.0",
URI: "test-uri",
SHA256: "test-sha256",
Stacks: []string{"test-stack-1", "test-stack-2"},
PURL: "pkg:generic/[email protected]",
}))
})

it("filters by version constraint", func() {
resolver.Dependencies = []libpak.BuildModuleDependency{
{
Expand Down
78 changes: 78 additions & 0 deletions crush/crush.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,84 @@ 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 {
Expand Down
26 changes: 26 additions & 0 deletions crush/crush_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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"), 0700)).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
`), 0600)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF"), 0700)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "classes"), 0700)).To(Succeed())
Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "OtherClass.class"), []byte(""), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(path, "BOOT-INF", "classes", "YetOther.class"), []byte(""), 0600)).To(Succeed())
Expect(os.MkdirAll(filepath.Join(path, "BOOT-INF", "lib"), 0700)).To(Succeed())
os.Symlink(filepath.Join(cwd, "testdata", "test-archive.jar"), filepath.Join(path, "BOOT-INF", "lib", "test-archive.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", "test-archive.jar")).To(BeARegularFile())
})
})

context("Extract", func() {
Expand Down
Binary file added crush/testdata/test-archive.jar
Binary file not shown.
Loading

0 comments on commit 796159f

Please sign in to comment.