diff --git a/go.mod b/go.mod index 2cdca20c..51a1747b 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/gabriel-vasile/mimetype v1.4.7 github.com/google/uuid v1.6.0 + github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.34.1 github.com/pelletier/go-toml v1.9.5 github.com/sclevine/spec v1.4.0 diff --git a/go.sum b/go.sum index fa109cff..5d585e82 100644 --- a/go.sum +++ b/go.sum @@ -1108,6 +1108,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= @@ -1657,6 +1658,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -3100,6 +3102,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= diff --git a/postal/buildpack.go b/postal/buildpack.go index df3d5498..157c5f0a 100644 --- a/postal/buildpack.go +++ b/postal/buildpack.go @@ -74,6 +74,9 @@ type Dependency struct { // StripComponents behaves like the --strip-components flag on tar command // removing the first n levels from the final decompression destination. StripComponents int `toml:"strip-components"` + + // ARCH is the architecture of this dependency + Arch string `toml:"arch"` } func parseBuildpack(path, name string) ([]Dependency, string, error) { diff --git a/postal/service.go b/postal/service.go index 3147b059..c6d54372 100644 --- a/postal/service.go +++ b/postal/service.go @@ -4,8 +4,10 @@ import ( "errors" "fmt" "io" + "os" "path/filepath" "regexp" + "runtime" "sort" "strings" "time" @@ -52,15 +54,17 @@ type ErrNoDeps struct { id string version string stack string + arch string supportedVersions []string } // Error implements the error.Error interface func (e *ErrNoDeps) Error() string { - return fmt.Sprintf("failed to satisfy %q dependency version constraint %q: no compatible versions on %q stack. Supported versions are: [%s]", + return fmt.Sprintf("failed to satisfy %q dependency version constraint %q: no compatible versions on %q stack with architecture %q. Supported versions are: [%s]", e.id, e.version, e.stack, + e.arch, strings.Join(e.supportedVersions, ", "), ) } @@ -147,6 +151,10 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { continue } + if !isCorrectArch(dependency) { + continue + } + sVersion, err := semver.NewVersion(dependency.Version) if err != nil { return Dependency{}, err @@ -160,7 +168,7 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { } if len(compatibleVersions) == 0 { - return Dependency{}, &ErrNoDeps{id, version, stack, supportedVersions} + return Dependency{}, &ErrNoDeps{id, version, stack, archFromSystem(), supportedVersions} } stacksForVersion := map[string][]string{} @@ -220,6 +228,25 @@ func (s Service) Resolve(path, id, version, stack string) (Dependency, error) { return compatibleVersions[0], nil } +func isCorrectArch(dep Dependency) bool { + systemArch := archFromSystem() + + if systemArch == "amd64" && dep.Arch == "" { + return true + } + + return systemArch == dep.Arch +} + +func archFromSystem() string { + archFromEnv, ok := os.LookupEnv("BP_ARCH") + if !ok { + archFromEnv = runtime.GOARCH + } + + return archFromEnv +} + func stringSliceContains(slice []string, str string) bool { for _, s := range slice { if s == str { diff --git a/postal/service_test.go b/postal/service_test.go index 25e54b2c..200a5c5f 100644 --- a/postal/service_test.go +++ b/postal/service_test.go @@ -23,6 +23,7 @@ import ( //nolint Ignore SA1019, usage of deprecated package within a deprecated test case "github.com/paketo-buildpacks/packit/v2/paketosbom" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -115,6 +116,12 @@ strip-components = 1 service = postal.NewService(transport). WithDependencyMappingResolver(mappingResolver). WithDependencyMirrorResolver(mirrorResolver) + + Expect(os.Setenv("BP_ARCH", "amd64")).To(Succeed()) + }) + + AfterEach(func() { + Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) }) context("Resolve", func() { @@ -310,6 +317,85 @@ version = "4.5.6" }) }) + context("when there architecture specific versions", func() { + it.Before(func() { + err := os.WriteFile(path, []byte(` +[metadata] +[metadata.default-versions] +some-entry = "1.2.x" + +[[metadata.dependencies]] +id = "some-entry" +sha256 = "some-sha-amd64" +stacks = ["*"] +uri = "some-uri" +version = "1.2.3" +arch = "amd64" + +[[metadata.dependencies]] +id = "some-entry" +sha256 = "some-sha-arm" +stacks = ["*"] +uri = "some-uri" +version = "1.2.3" +arch = "arm" + +[[metadata.dependencies]] +id = "some-other-entry" +sha256 = "some-other-sha" +stacks = ["*"] +uri = "some-uri" +version = "1.2.4" + +`), 0600) + Expect(err).NotTo(HaveOccurred()) + }) + + context("BP_ARCH=amd64", func() { + it.Before(func() { + Expect(os.Setenv("BP_ARCH", "amd64")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) + }) + + it("picks the dependency with the correct arch", func() { + dependency, err := service.Resolve(path, "some-entry", "1.2.3", "some-stack") + Expect(err).NotTo(HaveOccurred()) + Expect(dependency.SHA256).To(Equal("some-sha-amd64")) + }) + + it("picks the dependency with the no arch", func() { + dependency, err := service.Resolve(path, "some-other-entry", "1.2.4", "some-stack") + Expect(err).NotTo(HaveOccurred()) + Expect(dependency.SHA256).To(Equal("some-other-sha")) + }) + }) + + context("BP_ARCH=arm", func() { + it.Before(func() { + Expect(os.Setenv("BP_ARCH", "arm")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("BP_ARCH")).To(Succeed()) + }) + + it("picks the dependency with the correct arch", func() { + dependency, err := service.Resolve(path, "some-entry", "1.2.3", "some-stack") + Expect(err).NotTo(HaveOccurred()) + Expect(dependency.SHA256).To(Equal("some-sha-arm")) + }) + + it("fails if no dependency with the correct arch is found", func() { + _, err := service.Resolve(path, "some-other-entry", "1.2.4", "some-stack") + Expect(err).To(MatchError(ContainSubstring("failed to satisfy"))) + Expect(err).To(MatchError(ContainSubstring("\"arm\""))) + }) + }) + }) + context("when both a wildcard stack constraint and a specific stack constraint exist for the same dependency version", func() { it.Before(func() { err := os.WriteFile(path, []byte(` @@ -429,7 +515,7 @@ version = "1.2.3" expectedErr := &postal.ErrNoDeps{} _, err := service.Resolve(path, "some-entry", "9.9.9", "some-stack") Expect(errors.As(err, &expectedErr)).To(BeTrue()) - Expect(err).To(MatchError(ContainSubstring("failed to satisfy \"some-entry\" dependency version constraint \"9.9.9\": no compatible versions on \"some-stack\" stack. Supported versions are: [1.2.3, 4.5.6]"))) + Expect(err).To(MatchError(ContainSubstring("failed to satisfy \"some-entry\" dependency version constraint \"9.9.9\": no compatible versions on \"some-stack\" stack with architecture \"amd64\". Supported versions are: [1.2.3, 4.5.6]"))) }) }) })